From 0e84678150abe82503ba5253328e90ed3c21f682 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Fri, 3 Feb 2023 14:50:33 -0500 Subject: [PATCH 01/49] [PS-2416 and PS-2417] dont set CSP config value by default (#2667) * dont set CSP config value by default * space --- util/Setup/Configuration.cs | 8 +------- util/Setup/Context.cs | 15 +++++++++++++++ util/Setup/NginxConfigBuilder.cs | 15 ++++++++++++++- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/util/Setup/Configuration.cs b/util/Setup/Configuration.cs index bfe7b10d31..37afa09c3a 100644 --- a/util/Setup/Configuration.cs +++ b/util/Setup/Configuration.cs @@ -76,13 +76,7 @@ public class Configuration [Description("Nginx Header Content-Security-Policy parameter\n" + "WARNING: Reconfiguring this parameter may break features. By changing this parameter\n" + "you become responsible for maintaining this value.")] - public string NginxHeaderContentSecurityPolicy { get; set; } = "default-src 'self'; " + - "script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; " + - "img-src 'self' data: https://haveibeenpwned.com; " + - "child-src 'self' https://*.duosecurity.com https://*.duofederal.com; " + - "frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; " + - "connect-src 'self' wss://{0} https://api.pwnedpasswords.com " + - "https://api.2fa.directory; object-src 'self' blob:;"; + public string NginxHeaderContentSecurityPolicy { get; set; } [Description("Communicate with the Bitwarden push relay service (push.bitwarden.com) for mobile\n" + "app live sync.")] diff --git a/util/Setup/Context.cs b/util/Setup/Context.cs index f82e5005c6..c858cde04c 100644 --- a/util/Setup/Context.cs +++ b/util/Setup/Context.cs @@ -6,6 +6,14 @@ namespace Bit.Setup; public class Context { private const string ConfigPath = "/bitwarden/config.yml"; + // This keeps track of the value of the CSP that was defined as of Jan 2023. + // Do not change this value. + private const string Jan2023ContentSecurityPolicy = "default-src 'self'; style-src 'self' " + + "'unsafe-inline'; img-src 'self' data: https://haveibeenpwned.com; " + + "child-src 'self' https://*.duosecurity.com https://*.duofederal.com; " + + "frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; " + + "connect-src 'self' wss://{0} https://api.pwnedpasswords.com " + + "https://api.2fa.directory; object-src 'self' blob:;"; public string[] Args { get; set; } public bool Quiet { get; set; } @@ -117,6 +125,13 @@ public class Context .WithNamingConvention(UnderscoredNamingConvention.Instance) .Build(); Config = deserializer.Deserialize(configText); + + // Fix old explicit config assignments of CSP which should be treated as a default value + if (Config.NginxHeaderContentSecurityPolicy == Jan2023ContentSecurityPolicy) + { + Config.NginxHeaderContentSecurityPolicy = null; + SaveConfiguration(); + } } public void SaveConfiguration() diff --git a/util/Setup/NginxConfigBuilder.cs b/util/Setup/NginxConfigBuilder.cs index 420793cef7..865b8bdd69 100644 --- a/util/Setup/NginxConfigBuilder.cs +++ b/util/Setup/NginxConfigBuilder.cs @@ -4,6 +4,14 @@ public class NginxConfigBuilder { private const string ConfFile = "/bitwarden/nginx/default.conf"; + private const string DefaultContentSecurityPolicy = "default-src 'self'; " + + "script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; " + + "img-src 'self' data: https://haveibeenpwned.com; " + + "child-src 'self' https://*.duosecurity.com https://*.duofederal.com; " + + "frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; " + + "connect-src 'self' wss://{0} https://api.pwnedpasswords.com " + + "https://api.2fa.directory; object-src 'self' blob:;"; + private readonly Context _context; public NginxConfigBuilder(Context context) @@ -72,7 +80,12 @@ public class NginxConfigBuilder Domain = context.Config.Domain; Url = context.Config.Url; RealIps = context.Config.RealIps; - ContentSecurityPolicy = string.Format(context.Config.NginxHeaderContentSecurityPolicy, Domain); + var csp = DefaultContentSecurityPolicy; + if (!string.IsNullOrWhiteSpace(context.Config.NginxHeaderContentSecurityPolicy)) + { + csp = context.Config.NginxHeaderContentSecurityPolicy; + } + ContentSecurityPolicy = string.Format(csp, Domain); if (Ssl) { From 6514bdbb7eda5fba4ec6268e4bef4973bebcb8bc Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Fri, 3 Feb 2023 16:04:03 -0500 Subject: [PATCH 02/49] add more CSPs to the default correction check (#2668) * add more CSPs to the default correction check * add Dec2020ContentSecurityPolicy --- util/Setup/Context.cs | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/util/Setup/Context.cs b/util/Setup/Context.cs index c858cde04c..251c61f626 100644 --- a/util/Setup/Context.cs +++ b/util/Setup/Context.cs @@ -6,8 +6,26 @@ namespace Bit.Setup; public class Context { private const string ConfigPath = "/bitwarden/config.yml"; - // This keeps track of the value of the CSP that was defined as of Jan 2023. - // Do not change this value. + + // These track of old CSP default values to correct. + // Do not change these values. + private const string Dec2020ContentSecurityPolicy = "default-src 'self'; style-src 'self' " + + "'unsafe-inline'; img-src 'self' data: https://haveibeenpwned.com https://www.gravatar.com; " + + "child-src 'self' https://*.duosecurity.com; frame-src 'self' https://*.duosecurity.com; " + + "connect-src 'self' wss://{0} https://api.pwnedpasswords.com " + + "https://twofactorauth.org; object-src 'self' blob:;"; + private const string Jan2021ContentSecurityPolicy = "default-src 'self'; style-src 'self' " + + "'unsafe-inline'; img-src 'self' data: https://haveibeenpwned.com https://www.gravatar.com; " + + "child-src 'self' https://*.duosecurity.com https://*.duofederal.com; " + + "frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; " + + "connect-src 'self' wss://{0} https://api.pwnedpasswords.com " + + "https://twofactorauth.org; object-src 'self' blob:;"; + private const string Feb2021ContentSecurityPolicy = "default-src 'self'; style-src 'self' " + + "'unsafe-inline'; img-src 'self' data: https://haveibeenpwned.com https://www.gravatar.com; " + + "child-src 'self' https://*.duosecurity.com https://*.duofederal.com; " + + "frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; " + + "connect-src 'self' wss://{0} https://api.pwnedpasswords.com " + + "https://2fa.directory; object-src 'self' blob:;"; private const string Jan2023ContentSecurityPolicy = "default-src 'self'; style-src 'self' " + "'unsafe-inline'; img-src 'self' data: https://haveibeenpwned.com; " + "child-src 'self' https://*.duosecurity.com https://*.duofederal.com; " + @@ -15,6 +33,14 @@ public class Context "connect-src 'self' wss://{0} https://api.pwnedpasswords.com " + "https://api.2fa.directory; object-src 'self' blob:;"; + private string[] _oldCspDefaults = + { + Dec2020ContentSecurityPolicy, + Jan2021ContentSecurityPolicy, + Feb2021ContentSecurityPolicy, + Jan2023ContentSecurityPolicy + }; + public string[] Args { get; set; } public bool Quiet { get; set; } public bool Stub { get; set; } @@ -127,7 +153,7 @@ public class Context Config = deserializer.Deserialize(configText); // Fix old explicit config assignments of CSP which should be treated as a default value - if (Config.NginxHeaderContentSecurityPolicy == Jan2023ContentSecurityPolicy) + if (_oldCspDefaults.Any(c => c == Config.NginxHeaderContentSecurityPolicy)) { Config.NginxHeaderContentSecurityPolicy = null; SaveConfiguration(); From 55cc022d9bfbaa6c88aef3500357515addac456e Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 6 Feb 2023 10:27:40 +0100 Subject: [PATCH 03/49] [EC-1003] feat: remove `externalId` from PUT/POST (#2589) --- src/Api/Models/Request/GroupRequestModel.cs | 3 --- test/Api.Test/Controllers/GroupsControllerTests.cs | 6 ++---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Api/Models/Request/GroupRequestModel.cs b/src/Api/Models/Request/GroupRequestModel.cs index 6fcb34825a..aa52a08af2 100644 --- a/src/Api/Models/Request/GroupRequestModel.cs +++ b/src/Api/Models/Request/GroupRequestModel.cs @@ -10,8 +10,6 @@ public class GroupRequestModel public string Name { get; set; } [Required] public bool? AccessAll { get; set; } - [StringLength(300)] - public string ExternalId { get; set; } public IEnumerable Collections { get; set; } public IEnumerable Users { get; set; } @@ -27,7 +25,6 @@ public class GroupRequestModel { existingGroup.Name = Name; existingGroup.AccessAll = AccessAll.Value; - existingGroup.ExternalId = ExternalId; return existingGroup; } } diff --git a/test/Api.Test/Controllers/GroupsControllerTests.cs b/test/Api.Test/Controllers/GroupsControllerTests.cs index 5d700b4e36..b7f3b0cd36 100644 --- a/test/Api.Test/Controllers/GroupsControllerTests.cs +++ b/test/Api.Test/Controllers/GroupsControllerTests.cs @@ -29,7 +29,7 @@ public class GroupsControllerTests await sutProvider.GetDependency().Received(1).CreateGroupAsync( Arg.Is(g => g.OrganizationId == organization.Id && g.Name == groupRequestModel.Name && - g.AccessAll == groupRequestModel.AccessAll && g.ExternalId == groupRequestModel.ExternalId), + g.AccessAll == groupRequestModel.AccessAll), organization, Arg.Any>(), Arg.Any>()); @@ -38,7 +38,6 @@ public class GroupsControllerTests Assert.Equal(groupRequestModel.Name, response.Name); Assert.Equal(organization.Id.ToString(), response.OrganizationId); Assert.Equal(groupRequestModel.AccessAll, response.AccessAll); - Assert.Equal(groupRequestModel.ExternalId, response.ExternalId); } [Theory] @@ -57,7 +56,7 @@ public class GroupsControllerTests await sutProvider.GetDependency().Received(1).UpdateGroupAsync( Arg.Is(g => g.OrganizationId == organization.Id && g.Name == groupRequestModel.Name && - g.AccessAll == groupRequestModel.AccessAll && g.ExternalId == groupRequestModel.ExternalId), + g.AccessAll == groupRequestModel.AccessAll), Arg.Is(o => o.Id == organization.Id), Arg.Any>(), Arg.Any>()); @@ -66,6 +65,5 @@ public class GroupsControllerTests Assert.Equal(groupRequestModel.Name, response.Name); Assert.Equal(organization.Id.ToString(), response.OrganizationId); Assert.Equal(groupRequestModel.AccessAll, response.AccessAll); - Assert.Equal(groupRequestModel.ExternalId, response.ExternalId); } } From 113ee8a68095177cee23d08086f53787055bb3ab Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Mon, 6 Feb 2023 15:54:44 +0100 Subject: [PATCH 04/49] [EC-861] feat: Add external id to member api (#2594) --- .../Response/Organizations/OrganizationUserResponseModel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Api/Models/Response/Organizations/OrganizationUserResponseModel.cs b/src/Api/Models/Response/Organizations/OrganizationUserResponseModel.cs index 4c7424744c..13c1aaf658 100644 --- a/src/Api/Models/Response/Organizations/OrganizationUserResponseModel.cs +++ b/src/Api/Models/Response/Organizations/OrganizationUserResponseModel.cs @@ -23,6 +23,7 @@ public class OrganizationUserResponseModel : ResponseModel Type = organizationUser.Type; Status = organizationUser.Status; AccessAll = organizationUser.AccessAll; + ExternalId = organizationUser.ExternalId; AccessSecretsManager = organizationUser.AccessSecretsManager; Permissions = CoreHelpers.LoadClassFromJsonData(organizationUser.Permissions); ResetPasswordEnrolled = !string.IsNullOrEmpty(organizationUser.ResetPasswordKey); @@ -41,6 +42,7 @@ public class OrganizationUserResponseModel : ResponseModel Type = organizationUser.Type; Status = organizationUser.Status; AccessAll = organizationUser.AccessAll; + ExternalId = organizationUser.ExternalId; AccessSecretsManager = organizationUser.AccessSecretsManager; Permissions = CoreHelpers.LoadClassFromJsonData(organizationUser.Permissions); ResetPasswordEnrolled = !string.IsNullOrEmpty(organizationUser.ResetPasswordKey); @@ -52,6 +54,7 @@ public class OrganizationUserResponseModel : ResponseModel public OrganizationUserType Type { get; set; } public OrganizationUserStatusType Status { get; set; } public bool AccessAll { get; set; } + public string ExternalId { get; set; } public bool AccessSecretsManager { get; set; } public Permissions Permissions { get; set; } public bool ResetPasswordEnrolled { get; set; } From 9110efa44e1c02c7140d270cdfca0446902a8355 Mon Sep 17 00:00:00 2001 From: Brandon Maharaj Date: Mon, 6 Feb 2023 11:36:03 -0500 Subject: [PATCH 05/49] fix: update to 10 (#2670) --- src/SharedWeb/Utilities/ServiceCollectionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index fef0eb4857..95537a10c9 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -339,7 +339,7 @@ public static class ServiceCollectionExtensions { RequireDigit = false, RequireLowercase = false, - RequiredLength = 8, + RequiredLength = 10, RequireNonAlphanumeric = false, RequireUppercase = false }; From cf669286eda971e060a52b1852ba910df7e34a87 Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Mon, 6 Feb 2023 11:26:06 -0600 Subject: [PATCH 06/49] [SM-429] Add permission checks to access policy endpoints (#2628) * Add permission checks to access policy endpoints * Fix unit tests * Add service account grant permission checks * Add service account grant tests * Add new endpoint unit tests * Cleanup unit tests add integration tests * User permission enum in create tests * Swap to NotFoundException for access checks * Add filter for potential grantees * Add in AccessSecretsManager check and test it * Add code review updates * Code review updates * Refactor potential grantees endpoint * Code review updates --- .../CreateAccessPoliciesCommand.cs | 46 +- .../DeleteAccessPolicyCommand.cs | 88 +++- .../UpdateAccessPolicyCommand.cs | 87 +++- .../Repositories/AccessPolicyRepository.cs | 7 + .../Repositories/ServiceAccountRepository.cs | 17 + .../CreateAccessPoliciesCommandTests.cs | 135 ++++- .../DeleteAccessPolicyCommandTests.cs | 204 +++++++- .../UpdateAccessPolicyCommandTests.cs | 236 ++++++++- .../SecretsManager/Enums/AccessPolicyType.cs | 11 + .../SecretsManager/Enums/PermissionType.cs | 7 + .../Controllers/AccessPoliciesController.cs | 108 +++- .../Response/PotentialGranteeResponseModel.cs | 62 +++ .../ICreateAccessPoliciesCommand.cs | 2 +- .../Interfaces/IDeleteAccessPolicyCommand.cs | 2 +- .../Interfaces/IUpdateAccessPolicyCommand.cs | 2 +- .../SecretsManager/Entities/AccessPolicy.cs | 15 +- .../Repositories/IServiceAccountRepository.cs | 1 + .../AccessPoliciesControllerTest.cs | 484 +++++++++++++++--- .../SecretsManager/Enums/PermissionType.cs | 7 + .../AccessPoliciesControllerTests.cs | 335 +++++++++++- 20 files changed, 1710 insertions(+), 146 deletions(-) create mode 100644 bitwarden_license/test/Commercial.Core.Test/SecretsManager/Enums/AccessPolicyType.cs create mode 100644 bitwarden_license/test/Commercial.Core.Test/SecretsManager/Enums/PermissionType.cs create mode 100644 src/Api/SecretsManager/Models/Response/PotentialGranteeResponseModel.cs create mode 100644 test/Api.IntegrationTest/SecretsManager/Enums/PermissionType.cs diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs index 05af497e65..2b003aae48 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs @@ -1,4 +1,6 @@ -using Bit.Core.Exceptions; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; @@ -8,22 +10,52 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand { private readonly IAccessPolicyRepository _accessPolicyRepository; + private readonly ICurrentContext _currentContext; + private readonly IProjectRepository _projectRepository; - public CreateAccessPoliciesCommand(IAccessPolicyRepository accessPolicyRepository) + public CreateAccessPoliciesCommand( + IAccessPolicyRepository accessPolicyRepository, + ICurrentContext currentContext, + IProjectRepository projectRepository) { + _projectRepository = projectRepository; _accessPolicyRepository = accessPolicyRepository; + _currentContext = currentContext; } - public async Task> CreateAsync(List accessPolicies) + public async Task> CreateForProjectAsync(Guid projectId, + List accessPolicies, Guid userId) { + var project = await _projectRepository.GetByIdAsync(projectId); + if (project == null || !_currentContext.AccessSecretsManager(project.OrganizationId)) + { + throw new NotFoundException(); + } + + var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId); + var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + + var hasAccess = accessClient switch + { + AccessClientType.NoAccessCheck => true, + AccessClientType.User => await _projectRepository.UserHasWriteAccessToProject(project.Id, userId), + _ => false, + }; + + if (!hasAccess) + { + throw new NotFoundException(); + } + var distinctAccessPolicies = accessPolicies.DistinctBy(baseAccessPolicy => { return baseAccessPolicy switch { UserProjectAccessPolicy ap => new Tuple(ap.OrganizationUserId, ap.GrantedProjectId), GroupProjectAccessPolicy ap => new Tuple(ap.GroupId, ap.GrantedProjectId), - ServiceAccountProjectAccessPolicy ap => new Tuple(ap.ServiceAccountId, ap.GrantedProjectId), - _ => throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)) + ServiceAccountProjectAccessPolicy ap => new Tuple(ap.ServiceAccountId, + ap.GrantedProjectId), + _ => throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)), }; }).ToList(); @@ -39,7 +71,7 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand throw new BadRequestException("Resource already exists"); } } - - return await _accessPolicyRepository.CreateManyAsync(accessPolicies); + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + return await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(projectId); } } diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/DeleteAccessPolicyCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/DeleteAccessPolicyCommand.cs index 0795d62905..ad5b3da14c 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/DeleteAccessPolicyCommand.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/DeleteAccessPolicyCommand.cs @@ -1,5 +1,8 @@ -using Bit.Core.Exceptions; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; +using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; @@ -7,14 +10,23 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; public class DeleteAccessPolicyCommand : IDeleteAccessPolicyCommand { private readonly IAccessPolicyRepository _accessPolicyRepository; + private readonly ICurrentContext _currentContext; + private readonly IProjectRepository _projectRepository; + private readonly IServiceAccountRepository _serviceAccountRepository; - public DeleteAccessPolicyCommand(IAccessPolicyRepository accessPolicyRepository) + public DeleteAccessPolicyCommand( + IAccessPolicyRepository accessPolicyRepository, + ICurrentContext currentContext, + IProjectRepository projectRepository, + IServiceAccountRepository serviceAccountRepository) { + _projectRepository = projectRepository; + _serviceAccountRepository = serviceAccountRepository; _accessPolicyRepository = accessPolicyRepository; + _currentContext = currentContext; } - - public async Task DeleteAsync(Guid id) + public async Task DeleteAsync(Guid id, Guid userId) { var accessPolicy = await _accessPolicyRepository.GetByIdAsync(id); if (accessPolicy == null) @@ -22,6 +34,74 @@ public class DeleteAccessPolicyCommand : IDeleteAccessPolicyCommand throw new NotFoundException(); } + if (!await IsAllowedToDeleteAsync(accessPolicy, userId)) + { + throw new NotFoundException(); + } + await _accessPolicyRepository.DeleteAsync(id); } + + private async Task IsAllowedToDeleteAsync(BaseAccessPolicy baseAccessPolicy, Guid userId) => + baseAccessPolicy switch + { + UserProjectAccessPolicy ap => await HasPermissionAsync(ap.GrantedProject!.OrganizationId, userId, + ap.GrantedProjectId), + GroupProjectAccessPolicy ap => await HasPermissionAsync(ap.GrantedProject!.OrganizationId, userId, + ap.GrantedProjectId), + ServiceAccountProjectAccessPolicy ap => await HasPermissionAsync(ap.GrantedProject!.OrganizationId, + userId, ap.GrantedProjectId), + UserServiceAccountAccessPolicy ap => await HasPermissionAsync( + ap.GrantedServiceAccount!.OrganizationId, + userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId), + GroupServiceAccountAccessPolicy ap => await HasPermissionAsync( + ap.GrantedServiceAccount!.OrganizationId, + userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId), + _ => throw new ArgumentException("Unsupported access policy type provided."), + }; + + private async Task HasPermissionAsync( + Guid organizationId, + Guid userId, + Guid? projectIdToCheck = null, + Guid? serviceAccountIdToCheck = null) + { + if (!_currentContext.AccessSecretsManager(organizationId)) + { + return false; + } + + var orgAdmin = await _currentContext.OrganizationAdmin(organizationId); + var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + + bool hasAccess; + switch (accessClient) + { + case AccessClientType.NoAccessCheck: + hasAccess = true; + break; + case AccessClientType.User: + if (projectIdToCheck.HasValue) + { + hasAccess = await _projectRepository.UserHasWriteAccessToProject(projectIdToCheck.Value, userId); + } + else if (serviceAccountIdToCheck.HasValue) + { + hasAccess = + await _serviceAccountRepository.UserHasWriteAccessToServiceAccount( + serviceAccountIdToCheck.Value, userId); + } + else + { + throw new ArgumentException("No ID to check provided."); + } + + break; + default: + hasAccess = false; + break; + } + + return hasAccess; + } } diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/UpdateAccessPolicyCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/UpdateAccessPolicyCommand.cs index b7ebd1706b..bf83b735cf 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/UpdateAccessPolicyCommand.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/UpdateAccessPolicyCommand.cs @@ -1,4 +1,6 @@ -using Bit.Core.Exceptions; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; @@ -8,13 +10,23 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; public class UpdateAccessPolicyCommand : IUpdateAccessPolicyCommand { private readonly IAccessPolicyRepository _accessPolicyRepository; + private readonly ICurrentContext _currentContext; + private readonly IProjectRepository _projectRepository; + private readonly IServiceAccountRepository _serviceAccountRepository; - public UpdateAccessPolicyCommand(IAccessPolicyRepository accessPolicyRepository) + public UpdateAccessPolicyCommand( + IAccessPolicyRepository accessPolicyRepository, + ICurrentContext currentContext, + IProjectRepository projectRepository, + IServiceAccountRepository serviceAccountRepository) { _accessPolicyRepository = accessPolicyRepository; + _currentContext = currentContext; + _projectRepository = projectRepository; + _serviceAccountRepository = serviceAccountRepository; } - public async Task UpdateAsync(Guid id, bool read, bool write) + public async Task UpdateAsync(Guid id, bool read, bool write, Guid userId) { var accessPolicy = await _accessPolicyRepository.GetByIdAsync(id); if (accessPolicy == null) @@ -22,11 +34,78 @@ public class UpdateAccessPolicyCommand : IUpdateAccessPolicyCommand throw new NotFoundException(); } + if (!await IsAllowedToUpdateAsync(accessPolicy, userId)) + { + throw new NotFoundException(); + } + accessPolicy.Read = read; accessPolicy.Write = write; accessPolicy.RevisionDate = DateTime.UtcNow; - await _accessPolicyRepository.ReplaceAsync(accessPolicy); return accessPolicy; } + + private async Task IsAllowedToUpdateAsync(BaseAccessPolicy baseAccessPolicy, Guid userId) => + baseAccessPolicy switch + { + UserProjectAccessPolicy ap => await HasPermissionsAsync(ap.GrantedProject!.OrganizationId, userId, + ap.GrantedProjectId), + GroupProjectAccessPolicy ap => await HasPermissionsAsync(ap.GrantedProject!.OrganizationId, userId, + ap.GrantedProjectId), + ServiceAccountProjectAccessPolicy ap => await HasPermissionsAsync(ap.GrantedProject!.OrganizationId, + userId, ap.GrantedProjectId), + UserServiceAccountAccessPolicy ap => await HasPermissionsAsync( + ap.GrantedServiceAccount!.OrganizationId, + userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId), + GroupServiceAccountAccessPolicy ap => await HasPermissionsAsync( + ap.GrantedServiceAccount!.OrganizationId, + userId, serviceAccountIdToCheck: ap.GrantedServiceAccountId), + _ => throw new ArgumentException("Unsupported access policy type provided."), + }; + + private async Task HasPermissionsAsync( + Guid organizationId, + Guid userId, + Guid? projectIdToCheck = null, + Guid? serviceAccountIdToCheck = null) + { + if (!_currentContext.AccessSecretsManager(organizationId)) + { + return false; + } + + var orgAdmin = await _currentContext.OrganizationAdmin(organizationId); + var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + + bool hasAccess; + switch (accessClient) + { + case AccessClientType.NoAccessCheck: + hasAccess = true; + break; + case AccessClientType.User: + if (projectIdToCheck.HasValue) + { + hasAccess = await _projectRepository.UserHasWriteAccessToProject(projectIdToCheck.Value, userId); + } + else if (serviceAccountIdToCheck.HasValue) + { + hasAccess = + await _serviceAccountRepository.UserHasWriteAccessToServiceAccount( + serviceAccountIdToCheck.Value, userId); + } + else + { + throw new ArgumentException("No ID to check provided."); + } + + break; + default: + hasAccess = false; + break; + } + + return hasAccess; + } } diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs index 1cf066190e..b61bdcbfd7 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs @@ -108,8 +108,15 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli var dbContext = GetDatabaseContext(scope); var entity = await dbContext.AccessPolicies.Where(ap => ap.Id == id) .Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User) + .Include(ap => ((UserProjectAccessPolicy)ap).GrantedProject) .Include(ap => ((GroupProjectAccessPolicy)ap).Group) + .Include(ap => ((GroupProjectAccessPolicy)ap).GrantedProject) .Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount) + .Include(ap => ((ServiceAccountProjectAccessPolicy)ap).GrantedProject) + .Include(ap => ((UserServiceAccountAccessPolicy)ap).OrganizationUser.User) + .Include(ap => ((UserServiceAccountAccessPolicy)ap).GrantedServiceAccount) + .Include(ap => ((GroupServiceAccountAccessPolicy)ap).Group) + .Include(ap => ((GroupServiceAccountAccessPolicy)ap).GrantedServiceAccount) .FirstOrDefaultAsync(); if (entity == null) diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ServiceAccountRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ServiceAccountRepository.cs index 4632abc8f7..5dd560c456 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ServiceAccountRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ServiceAccountRepository.cs @@ -54,6 +54,23 @@ public class ServiceAccountRepository : Repository> GetManyByOrganizationIdWriteAccessAsync(Guid organizationId, Guid userId, AccessClientType accessType) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var query = dbContext.ServiceAccount.Where(c => c.OrganizationId == organizationId); + + query = accessType switch + { + AccessClientType.NoAccessCheck => query, + AccessClientType.User => query.Where(UserHasWriteAccessToServiceAccount(userId)), + _ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null), + }; + + var serviceAccounts = await query.OrderBy(c => c.RevisionDate).ToListAsync(); + return Mapper.Map>(serviceAccounts); + } + private static Expression> UserHasReadAccessToServiceAccount(Guid userId) => sa => sa.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Read) || sa.GroupAccessPolicies.Any(ap => ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Read)); diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs index 6005f1a9a9..451caeffeb 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs @@ -1,7 +1,10 @@ using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; +using Bit.Commercial.Core.Test.SecretsManager.Enums; +using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; @@ -11,29 +14,14 @@ using Xunit; namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies; [SutProviderCustomize] +[ProjectCustomize] public class CreateAccessPoliciesCommandTests { [Theory] [BitAutoData] - public async Task CreateAsync_CallsCreate(List userProjectAccessPolicies, - List groupProjectAccessPolicies, - List serviceAccountProjectAccessPolicies, - SutProvider sutProvider) - { - var data = new List(); - data.AddRange(userProjectAccessPolicies); - data.AddRange(groupProjectAccessPolicies); - data.AddRange(serviceAccountProjectAccessPolicies); - - await sutProvider.Sut.CreateAsync(data); - - await sutProvider.GetDependency().Received(1) - .CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data))); - } - - [Theory] - [BitAutoData] - public async Task CreateAsync_AlreadyExists_Throws_BadRequestException( + public async Task CreateAsync_SmNotEnabled_Throws( + Guid userId, + Project project, List userProjectAccessPolicies, List groupProjectAccessPolicies, List serviceAccountProjectAccessPolicies, @@ -44,14 +32,41 @@ public class CreateAccessPoliciesCommandTests data.AddRange(groupProjectAccessPolicies); data.AddRange(serviceAccountProjectAccessPolicies); - sutProvider.GetDependency().AccessPolicyExists(Arg.Any()) - .Returns(true); + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(false); - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(data)); + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateManyAsync(default); } + [Theory] + [BitAutoData] + public async Task CreateAsync_AlreadyExists_Throws_BadRequestException( + Guid userId, + Project project, + List userProjectAccessPolicies, + List groupProjectAccessPolicies, + List serviceAccountProjectAccessPolicies, + SutProvider sutProvider) + { + var data = new List(); + data.AddRange(userProjectAccessPolicies); + data.AddRange(groupProjectAccessPolicies); + data.AddRange(serviceAccountProjectAccessPolicies); + + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); + sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); + sutProvider.GetDependency().OrganizationAdmin(project.OrganizationId).Returns(true); + + sutProvider.GetDependency().AccessPolicyExists(Arg.Any()) + .Returns(true); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateManyAsync(default); + } [Theory] [BitAutoData(true, false, false)] @@ -65,6 +80,8 @@ public class CreateAccessPoliciesCommandTests bool testUserPolicies, bool testGroupPolicies, bool testServiceAccountPolicies, + Guid userId, + Project project, List userProjectAccessPolicies, List groupProjectAccessPolicies, List serviceAccountProjectAccessPolicies, @@ -76,12 +93,16 @@ public class CreateAccessPoliciesCommandTests data.AddRange(groupProjectAccessPolicies); data.AddRange(serviceAccountProjectAccessPolicies); + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); + sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); + sutProvider.GetDependency().OrganizationAdmin(project.OrganizationId).Returns(true); + if (testUserPolicies) { var mockUserPolicy = new UserProjectAccessPolicy { OrganizationUserId = Guid.NewGuid(), - GrantedProjectId = Guid.NewGuid() + GrantedProjectId = Guid.NewGuid(), }; data.Add(mockUserPolicy); @@ -94,7 +115,7 @@ public class CreateAccessPoliciesCommandTests var mockGroupPolicy = new GroupProjectAccessPolicy { GroupId = Guid.NewGuid(), - GrantedProjectId = Guid.NewGuid() + GrantedProjectId = Guid.NewGuid(), }; data.Add(mockGroupPolicy); @@ -107,7 +128,7 @@ public class CreateAccessPoliciesCommandTests var mockServiceAccountPolicy = new ServiceAccountProjectAccessPolicy { ServiceAccountId = Guid.NewGuid(), - GrantedProjectId = Guid.NewGuid() + GrantedProjectId = Guid.NewGuid(), }; data.Add(mockServiceAccountPolicy); @@ -119,7 +140,69 @@ public class CreateAccessPoliciesCommandTests sutProvider.GetDependency().AccessPolicyExists(Arg.Any()) .Returns(true); - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(data)); + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateManyAsync(default); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async Task CreateAsync_Success( + PermissionType permissionType, + Guid userId, + Project project, + List userProjectAccessPolicies, + List groupProjectAccessPolicies, + List serviceAccountProjectAccessPolicies, + SutProvider sutProvider) + { + var data = new List(); + data.AddRange(userProjectAccessPolicies); + data.AddRange(groupProjectAccessPolicies); + data.AddRange(serviceAccountProjectAccessPolicies); + + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); + sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); + + switch (permissionType) + { + case PermissionType.RunAsAdmin: + sutProvider.GetDependency().OrganizationAdmin(project.OrganizationId).Returns(true); + break; + case PermissionType.RunAsUserWithPermission: + sutProvider.GetDependency().UserHasWriteAccessToProject(project.Id, userId).Returns(true); + break; + } + + await sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId); + + await sutProvider.GetDependency().Received(1) + .CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data))); + } + + [Theory] + [BitAutoData] + public async Task CreateAsync_User_NoPermission( + Guid userId, + Project project, + List userProjectAccessPolicies, + List groupProjectAccessPolicies, + List serviceAccountProjectAccessPolicies, + SutProvider sutProvider) + { + var data = new List(); + data.AddRange(userProjectAccessPolicies); + data.AddRange(groupProjectAccessPolicies); + data.AddRange(serviceAccountProjectAccessPolicies); + + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); + sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); + sutProvider.GetDependency().UserHasWriteAccessToProject(project.Id, userId).Returns(false); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateManyAsync(default); } diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/DeleteAccessPolicyCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/DeleteAccessPolicyCommandTests.cs index dfb09eb440..c1c5743d7c 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/DeleteAccessPolicyCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/DeleteAccessPolicyCommandTests.cs @@ -1,7 +1,11 @@ using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; +using Bit.Commercial.Core.Test.SecretsManager.Enums; +using Bit.Core.Context; +using Bit.Core.Entities; using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -11,28 +15,216 @@ using Xunit; namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies; [SutProviderCustomize] +[ProjectCustomize] public class DeleteAccessPolicyCommandTests { + private static void SetupPermission(SutProvider sutProvider, + PermissionType permissionType, Project grantedProject, Guid userId) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + sutProvider.GetDependency().OrganizationAdmin(grantedProject.OrganizationId) + .Returns(true); + break; + case PermissionType.RunAsUserWithPermission: + sutProvider.GetDependency().UserHasWriteAccessToProject(grantedProject.Id, userId) + .Returns(true); + break; + default: + throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null); + } + } + + private static void SetupPermission(SutProvider sutProvider, + PermissionType permissionType, ServiceAccount grantedServiceAccount, Guid userId) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + sutProvider.GetDependency().OrganizationAdmin(grantedServiceAccount.OrganizationId) + .Returns(true); + break; + case PermissionType.RunAsUserWithPermission: + sutProvider.GetDependency() + .UserHasWriteAccessToServiceAccount(grantedServiceAccount.Id, userId) + .Returns(true); + break; + default: + throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null); + } + } + + private static BaseAccessPolicy CreatePolicyToReturn(AccessPolicyType accessPolicyType, Guid data, + Project grantedProject, Group mockGroup, ServiceAccount mockServiceAccount) => + accessPolicyType switch + { + AccessPolicyType.UserProjectAccessPolicy => new UserProjectAccessPolicy + { + Id = data, + GrantedProjectId = grantedProject.Id, + GrantedProject = grantedProject, + }, + AccessPolicyType.GroupProjectAccessPolicy => new GroupProjectAccessPolicy + { + Id = data, + GrantedProjectId = grantedProject.Id, + Group = mockGroup, + GrantedProject = grantedProject, + }, + AccessPolicyType.ServiceAccountProjectAccessPolicy => new ServiceAccountProjectAccessPolicy + { + Id = data, + GrantedProjectId = grantedProject.Id, + ServiceAccount = mockServiceAccount, + GrantedProject = grantedProject, + }, + _ => null, + }; + + private static BaseAccessPolicy CreatePolicyToReturn(AccessPolicyType accessPolicyType, Guid data, + ServiceAccount grantedServiceAccount, Group mockGroup) => + accessPolicyType switch + { + AccessPolicyType.UserServiceAccountAccessPolicy => new UserServiceAccountAccessPolicy + { + Id = data, + GrantedServiceAccountId = grantedServiceAccount.Id, + GrantedServiceAccount = grantedServiceAccount, + }, + AccessPolicyType.GroupServiceAccountAccessPolicy => new GroupServiceAccountAccessPolicy + { + Id = data, + GrantedServiceAccountId = grantedServiceAccount.Id, + Group = mockGroup, + GrantedServiceAccount = grantedServiceAccount, + }, + _ => null, + }; + [Theory] [BitAutoData] - public async Task DeleteAccessPolicy_Throws_NotFoundException(Guid data, + public async Task DeleteAccessPolicy_Throws_NotFoundException(Guid data, Guid userId, SutProvider sutProvider) { + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); sutProvider.GetDependency().GetByIdAsync(data).ReturnsNull(); - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteAsync(data)); + await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteAsync(data, userId)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().DeleteAsync(default); } [Theory] [BitAutoData] - public async Task DeleteAccessPolicy_Success(Guid data, + public async Task DeleteAccessPolicy_SmNotEnabled_Throws_NotFoundException(Guid data, Guid userId, SutProvider sutProvider) { - sutProvider.GetDependency().GetByIdAsync(data) - .Returns(new UserProjectAccessPolicy { Id = data }); + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(false); + sutProvider.GetDependency().GetByIdAsync(data).ReturnsNull(); + await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteAsync(data, userId)); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().DeleteAsync(default); + } - await sutProvider.Sut.DeleteAsync(data); + [Theory] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsAdmin)] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsAdmin)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsAdmin)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsUserWithPermission)] + public async Task DeleteAccessPolicy_ProjectGrants_PermissionsCheck_Success( + AccessPolicyType accessPolicyType, + PermissionType permissionType, + Guid data, + Guid userId, + Project grantedProject, + Group mockGroup, + ServiceAccount mockServiceAccount, + SutProvider sutProvider) + { + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); + var policyToReturn = + CreatePolicyToReturn(accessPolicyType, data, grantedProject, mockGroup, mockServiceAccount); + SetupPermission(sutProvider, permissionType, grantedProject, userId); + + sutProvider.GetDependency().GetByIdAsync(data) + .Returns(policyToReturn); + + await sutProvider.Sut.DeleteAsync(data, userId); await sutProvider.GetDependency().Received(1).DeleteAsync(Arg.Is(data)); } + + [Theory] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)] + public async Task DeleteAccessPolicy_UserProjectAccessPolicy_PermissionsCheck_ThrowsNotAuthorized( + AccessPolicyType accessPolicyType, + Guid data, + Guid userId, + Group mockGroup, + ServiceAccount mockServiceAccount, + Project grantedProject, + SutProvider sutProvider) + { + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); + var policyToReturn = + CreatePolicyToReturn(accessPolicyType, data, grantedProject, mockGroup, mockServiceAccount); + + sutProvider.GetDependency().GetByIdAsync(data) + .Returns(policyToReturn); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.DeleteAsync(data, userId)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().DeleteAsync(default); + } + + [Theory] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsAdmin)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsAdmin)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission)] + public async Task DeleteAccessPolicy_ServiceAccountGrants_PermissionsCheck_Success( + AccessPolicyType accessPolicyType, + PermissionType permissionType, + Guid data, + Guid userId, + ServiceAccount grantedServiceAccount, + Group mockGroup, + SutProvider sutProvider) + { + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); + var policyToReturn = CreatePolicyToReturn(accessPolicyType, data, grantedServiceAccount, mockGroup); + SetupPermission(sutProvider, permissionType, grantedServiceAccount, userId); + sutProvider.GetDependency().GetByIdAsync(data) + .Returns(policyToReturn); + + await sutProvider.Sut.DeleteAsync(data, userId); + + await sutProvider.GetDependency().Received(1).DeleteAsync(Arg.Is(data)); + } + + [Theory] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)] + public async Task DeleteAccessPolicy_ServiceAccountGrants_PermissionsCheck_Throws( + AccessPolicyType accessPolicyType, + Guid data, + Guid userId, + ServiceAccount grantedServiceAccount, + Group mockGroup, + SutProvider sutProvider) + { + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); + var policyToReturn = CreatePolicyToReturn(accessPolicyType, data, grantedServiceAccount, mockGroup); + + sutProvider.GetDependency().GetByIdAsync(data) + .Returns(policyToReturn); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.DeleteAsync(data, userId)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().DeleteAsync(default); + } } diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/UpdateAccessPolicyCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/UpdateAccessPolicyCommandTests.cs index 482be48391..a0f69c9f25 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/UpdateAccessPolicyCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/UpdateAccessPolicyCommandTests.cs @@ -1,7 +1,11 @@ using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; +using Bit.Commercial.Core.Test.SecretsManager.Enums; +using Bit.Core.Context; +using Bit.Core.Entities; using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; @@ -11,30 +15,246 @@ using Xunit; namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies; [SutProviderCustomize] +[ProjectCustomize] public class UpdateAccessPolicyCommandTests { + private static void SetupPermission(SutProvider sutProvider, + PermissionType permissionType, Project grantedProject, Guid userId) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + sutProvider.GetDependency().OrganizationAdmin(grantedProject.OrganizationId) + .Returns(true); + break; + case PermissionType.RunAsUserWithPermission: + sutProvider.GetDependency().UserHasWriteAccessToProject(grantedProject.Id, userId) + .Returns(true); + break; + default: + throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null); + } + } + + private static void SetupPermission(SutProvider sutProvider, + PermissionType permissionType, ServiceAccount grantedServiceAccount, Guid userId) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + sutProvider.GetDependency().OrganizationAdmin(grantedServiceAccount.OrganizationId) + .Returns(true); + break; + case PermissionType.RunAsUserWithPermission: + sutProvider.GetDependency() + .UserHasWriteAccessToServiceAccount(grantedServiceAccount.Id, userId) + .Returns(true); + break; + default: + throw new ArgumentOutOfRangeException(nameof(permissionType), permissionType, null); + } + } + + private static BaseAccessPolicy CreatePolicyToReturn(AccessPolicyType accessPolicyType, + ServiceAccount grantedServiceAccount, Guid data, Group mockGroup) + { + switch (accessPolicyType) + { + case AccessPolicyType.UserServiceAccountAccessPolicy: + return + new UserServiceAccountAccessPolicy + { + Id = data, + Read = true, + Write = true, + GrantedServiceAccountId = grantedServiceAccount.Id, + GrantedServiceAccount = grantedServiceAccount, + }; + case AccessPolicyType.GroupServiceAccountAccessPolicy: + mockGroup.OrganizationId = grantedServiceAccount.OrganizationId; + return new GroupServiceAccountAccessPolicy + { + Id = data, + GrantedServiceAccountId = grantedServiceAccount.Id, + GrantedServiceAccount = grantedServiceAccount, + Read = true, + Write = true, + Group = mockGroup, + }; + default: + throw new ArgumentOutOfRangeException(nameof(accessPolicyType), accessPolicyType, null); + } + } + + private static BaseAccessPolicy CreatePolicyToReturn(AccessPolicyType accessPolicyType, Guid data, + Project grantedProject, Group mockGroup, ServiceAccount mockServiceAccount) + { + switch (accessPolicyType) + { + case AccessPolicyType.UserProjectAccessPolicy: + return + new UserProjectAccessPolicy + { + Id = data, + Read = true, + Write = true, + GrantedProjectId = grantedProject.Id, + GrantedProject = grantedProject, + }; + case AccessPolicyType.GroupProjectAccessPolicy: + mockGroup.OrganizationId = grantedProject.OrganizationId; + return + new GroupProjectAccessPolicy + { + Id = data, + GrantedProjectId = grantedProject.Id, + Read = true, + Write = true, + Group = mockGroup, + GrantedProject = grantedProject, + }; + case AccessPolicyType.ServiceAccountProjectAccessPolicy: + mockServiceAccount.OrganizationId = grantedProject.OrganizationId; + return new ServiceAccountProjectAccessPolicy + { + Id = data, + GrantedProjectId = grantedProject.Id, + Read = true, + Write = true, + ServiceAccount = mockServiceAccount, + GrantedProject = grantedProject, + }; + default: + throw new ArgumentOutOfRangeException(nameof(accessPolicyType), accessPolicyType, null); + } + } + [Theory] [BitAutoData] - public async Task UpdateAsync_Throws_NotFoundException(Guid data, bool read, bool write, + public async Task UpdateAsync_Throws_NotFoundException(Guid data, bool read, bool write, Guid userId, SutProvider sutProvider) { - var exception = - await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(data, read, write)); + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); + await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(data, read, write, userId)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); } [Theory] [BitAutoData] - public async Task UpdateAsync_Calls_Replace(Guid data, bool read, bool write, + public async Task UpdateAsync_SmNotEnabled_Throws_NotFoundException(Guid data, bool read, bool write, Guid userId, SutProvider sutProvider) { - var existingPolicy = new UserProjectAccessPolicy { Id = data, Read = true, Write = true }; - sutProvider.GetDependency().GetByIdAsync(data).Returns(existingPolicy); - var result = await sutProvider.Sut.UpdateAsync(data, read, write); - await sutProvider.GetDependency().Received(1).ReplaceAsync(existingPolicy); + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(false); + await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(data, read, write, userId)); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); + } + + [Theory] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsAdmin)] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy, PermissionType.RunAsUserWithPermission)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsAdmin)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy, PermissionType.RunAsUserWithPermission)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsAdmin)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy, PermissionType.RunAsUserWithPermission)] + public async Task UpdateAsync_ProjectGrants_PermissionsCheck_Success( + AccessPolicyType accessPolicyType, + PermissionType permissionType, + Guid data, + bool read, + bool write, + Guid userId, + Project grantedProject, + Group mockGroup, + ServiceAccount mockServiceAccount, + SutProvider sutProvider) + { + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); + var policyToReturn = + CreatePolicyToReturn(accessPolicyType, data, grantedProject, mockGroup, mockServiceAccount); + SetupPermission(sutProvider, permissionType, grantedProject, userId); + + sutProvider.GetDependency().GetByIdAsync(data).Returns(policyToReturn); + var result = await sutProvider.Sut.UpdateAsync(data, read, write, userId); + await sutProvider.GetDependency().Received(1).ReplaceAsync(policyToReturn); AssertHelper.AssertRecent(result.RevisionDate); Assert.Equal(read, result.Read); Assert.Equal(write, result.Write); } + + [Theory] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)] + public async Task UpdateAsync_ProjectGrants_PermissionsCheck_Throws( + AccessPolicyType accessPolicyType, + Guid data, + bool read, + bool write, + Guid userId, + Project grantedProject, + Group mockGroup, + ServiceAccount mockServiceAccount, + SutProvider sutProvider) + { + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); + var policyToReturn = + CreatePolicyToReturn(accessPolicyType, data, grantedProject, mockGroup, mockServiceAccount); + sutProvider.GetDependency().GetByIdAsync(data).Returns(policyToReturn); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.UpdateAsync(data, read, write, userId)); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); + } + + [Theory] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsAdmin)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsAdmin)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy, PermissionType.RunAsUserWithPermission)] + public async Task UpdateAsync_ServiceAccountGrants_PermissionsCheck_Success( + AccessPolicyType accessPolicyType, + PermissionType permissionType, + Guid data, + bool read, + bool write, + Guid userId, + ServiceAccount grantedServiceAccount, + Group mockGroup, + SutProvider sutProvider) + { + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); + var policyToReturn = CreatePolicyToReturn(accessPolicyType, grantedServiceAccount, data, mockGroup); + SetupPermission(sutProvider, permissionType, grantedServiceAccount, userId); + + sutProvider.GetDependency().GetByIdAsync(data).Returns(policyToReturn); + var result = await sutProvider.Sut.UpdateAsync(data, read, write, userId); + await sutProvider.GetDependency().Received(1).ReplaceAsync(policyToReturn); + + AssertHelper.AssertRecent(result.RevisionDate); + Assert.Equal(read, result.Read); + Assert.Equal(write, result.Write); + } + + [Theory] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)] + public async Task UpdateAsync_ServiceAccountGrants_PermissionsCheck_Throws( + AccessPolicyType accessPolicyType, + Guid data, + bool read, + bool write, + Guid userId, + ServiceAccount grantedServiceAccount, + Group mockGroup, + SutProvider sutProvider) + { + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); + var policyToReturn = CreatePolicyToReturn(accessPolicyType, grantedServiceAccount, data, mockGroup); + sutProvider.GetDependency().GetByIdAsync(data).Returns(policyToReturn); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.UpdateAsync(data, read, write, userId)); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); + } } diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Enums/AccessPolicyType.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Enums/AccessPolicyType.cs new file mode 100644 index 0000000000..c46505b162 --- /dev/null +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Enums/AccessPolicyType.cs @@ -0,0 +1,11 @@ +namespace Bit.Commercial.Core.Test.SecretsManager.Enums; + +public enum AccessPolicyType +{ + UserProjectAccessPolicy, + GroupProjectAccessPolicy, + ServiceAccountProjectAccessPolicy, + UserServiceAccountAccessPolicy, + GroupServiceAccountAccessPolicy, + +} diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Enums/PermissionType.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Enums/PermissionType.cs new file mode 100644 index 0000000000..1c4e88e91c --- /dev/null +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Enums/PermissionType.cs @@ -0,0 +1,7 @@ +namespace Bit.Commercial.Core.Test.SecretsManager.Enums; + +public enum PermissionType +{ + RunAsAdmin, + RunAsUserWithPermission, +} diff --git a/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs b/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs index d2d3a4f28b..254dd09e24 100644 --- a/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs +++ b/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs @@ -1,27 +1,53 @@ -using Bit.Api.SecretsManager.Models.Request; +using Bit.Api.Models.Response; +using Bit.Api.SecretsManager.Models.Request; using Bit.Api.SecretsManager.Models.Response; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Repositories; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Services; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Bit.Api.SecretsManager.Controllers; [SecretsManager] +[Authorize("secrets")] [Route("access-policies")] public class AccessPoliciesController : Controller { private readonly IAccessPolicyRepository _accessPolicyRepository; private readonly ICreateAccessPoliciesCommand _createAccessPoliciesCommand; + private readonly ICurrentContext _currentContext; private readonly IDeleteAccessPolicyCommand _deleteAccessPolicyCommand; + private readonly IGroupRepository _groupRepository; + private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly IProjectRepository _projectRepository; + private readonly IServiceAccountRepository _serviceAccountRepository; private readonly IUpdateAccessPolicyCommand _updateAccessPolicyCommand; + private readonly IUserService _userService; public AccessPoliciesController( + IUserService userService, + ICurrentContext currentContext, IAccessPolicyRepository accessPolicyRepository, + IServiceAccountRepository serviceAccountRepository, + IGroupRepository groupRepository, + IProjectRepository projectRepository, + IOrganizationUserRepository organizationUserRepository, ICreateAccessPoliciesCommand createAccessPoliciesCommand, IDeleteAccessPolicyCommand deleteAccessPolicyCommand, IUpdateAccessPolicyCommand updateAccessPolicyCommand) { + _userService = userService; + _currentContext = currentContext; + _serviceAccountRepository = serviceAccountRepository; + _projectRepository = projectRepository; + _groupRepository = groupRepository; + _organizationUserRepository = organizationUserRepository; _accessPolicyRepository = accessPolicyRepository; _createAccessPoliciesCommand = createAccessPoliciesCommand; _deleteAccessPolicyCommand = deleteAccessPolicyCommand; @@ -32,14 +58,18 @@ public class AccessPoliciesController : Controller public async Task CreateProjectAccessPoliciesAsync([FromRoute] Guid id, [FromBody] AccessPoliciesCreateRequest request) { + var userId = _userService.GetProperUserId(User).Value; var policies = request.ToBaseAccessPoliciesForProject(id); - var results = await _createAccessPoliciesCommand.CreateAsync(policies); + var results = await _createAccessPoliciesCommand.CreateForProjectAsync(id, policies, userId); return new ProjectAccessPoliciesResponseModel(results); } [HttpGet("/projects/{id}/access-policies")] public async Task GetProjectAccessPoliciesAsync([FromRoute] Guid id) { + var project = await _projectRepository.GetByIdAsync(id); + await CheckUserHasWriteAccessToProjectAsync(project); + var results = await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(id); return new ProjectAccessPoliciesResponseModel(results); } @@ -48,7 +78,8 @@ public class AccessPoliciesController : Controller public async Task UpdateAccessPolicyAsync([FromRoute] Guid id, [FromBody] AccessPolicyUpdateRequest request) { - var result = await _updateAccessPolicyCommand.UpdateAsync(id, request.Read, request.Write); + var userId = _userService.GetProperUserId(User).Value; + var result = await _updateAccessPolicyCommand.UpdateAsync(id, request.Read, request.Write, userId); return result switch { @@ -56,13 +87,80 @@ public class AccessPoliciesController : Controller GroupProjectAccessPolicy accessPolicy => new GroupProjectAccessPolicyResponseModel(accessPolicy), ServiceAccountProjectAccessPolicy accessPolicy => new ServiceAccountProjectAccessPolicyResponseModel( accessPolicy), - _ => throw new ArgumentException("Unsupported access policy type provided.") + _ => throw new ArgumentException("Unsupported access policy type provided."), }; } [HttpDelete("{id}")] public async Task DeleteAccessPolicyAsync([FromRoute] Guid id) { - await _deleteAccessPolicyCommand.DeleteAsync(id); + var userId = _userService.GetProperUserId(User).Value; + await _deleteAccessPolicyCommand.DeleteAsync(id, userId); + } + + [HttpGet("/organizations/{id}/access-policies/people/potential-grantees")] + public async Task> GetPeoplePotentialGranteesAsync([FromRoute] Guid id) + { + if (!_currentContext.AccessSecretsManager(id)) + { + throw new NotFoundException(); + } + + var groups = await _groupRepository.GetManyByOrganizationIdAsync(id); + var groupResponses = groups.Select(g => new PotentialGranteeResponseModel(g)); + + var organizationUsers = + await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id); + var userResponses = organizationUsers + .Where(user => user.AccessSecretsManager) + .Select(userDetails => new PotentialGranteeResponseModel(userDetails)); + + return new ListResponseModel(userResponses.Concat(groupResponses)); + } + + [HttpGet("/organizations/{id}/access-policies/service-accounts/potential-grantees")] + public async Task> GetServiceAccountsPotentialGranteesAsync([FromRoute] Guid id) + { + if (!_currentContext.AccessSecretsManager(id)) + { + throw new NotFoundException(); + } + + var userId = _userService.GetProperUserId(User).Value; + var orgAdmin = await _currentContext.OrganizationAdmin(id); + var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + + var serviceAccounts = + await _serviceAccountRepository.GetManyByOrganizationIdWriteAccessAsync(id, + userId, + accessClient); + var serviceAccountResponses = + serviceAccounts.Select(serviceAccount => new PotentialGranteeResponseModel(serviceAccount)); + + return new ListResponseModel(serviceAccountResponses); + } + + private async Task CheckUserHasWriteAccessToProjectAsync(Project project) + { + if (project == null || !_currentContext.AccessSecretsManager(project.OrganizationId)) + { + throw new NotFoundException(); + } + + var userId = _userService.GetProperUserId(User).Value; + var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId); + var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + + var hasAccess = accessClient switch + { + AccessClientType.NoAccessCheck => true, + AccessClientType.User => await _projectRepository.UserHasWriteAccessToProject(project.Id, userId), + _ => false, + }; + + if (!hasAccess) + { + throw new NotFoundException(); + } } } diff --git a/src/Api/SecretsManager/Models/Response/PotentialGranteeResponseModel.cs b/src/Api/SecretsManager/Models/Response/PotentialGranteeResponseModel.cs new file mode 100644 index 0000000000..7b0632a459 --- /dev/null +++ b/src/Api/SecretsManager/Models/Response/PotentialGranteeResponseModel.cs @@ -0,0 +1,62 @@ +using Bit.Core.Entities; +using Bit.Core.Models.Api; +using Bit.Core.Models.Data.Organizations.OrganizationUsers; +using Bit.Core.SecretsManager.Entities; + +namespace Bit.Api.SecretsManager.Models.Response; + +public class PotentialGranteeResponseModel : ResponseModel +{ + private const string _objectName = "potentialGrantee"; + + public PotentialGranteeResponseModel(Group group) + : base(_objectName) + { + if (group == null) + { + throw new ArgumentNullException(nameof(group)); + } + + Id = group.Id.ToString(); + Name = group.Name; + Type = "group"; + } + + public PotentialGranteeResponseModel(OrganizationUserUserDetails user) + : base(_objectName) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + Id = user.Id.ToString(); + Name = user.Name; + Email = user.Email; + Type = "user"; + } + + public PotentialGranteeResponseModel(ServiceAccount serviceAccount) + : base(_objectName) + { + if (serviceAccount == null) + { + throw new ArgumentNullException(nameof(serviceAccount)); + } + + Id = serviceAccount.Id.ToString(); + Name = serviceAccount.Name; + Type = "serviceAccount"; + } + + public PotentialGranteeResponseModel() : base(_objectName) + { + } + + public string Id { get; set; } + + public string Name { get; set; } + + public string Type { get; set; } + public string? Email { get; set; } +} diff --git a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs index 9ec9ff0dbd..29aa34c0f7 100644 --- a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs +++ b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs @@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; public interface ICreateAccessPoliciesCommand { - Task> CreateAsync(List accessPolicies); + Task> CreateForProjectAsync(Guid projectId, List accessPolicies, Guid userId); } diff --git a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/IDeleteAccessPolicyCommand.cs b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/IDeleteAccessPolicyCommand.cs index de3215a02c..e4d313b409 100644 --- a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/IDeleteAccessPolicyCommand.cs +++ b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/IDeleteAccessPolicyCommand.cs @@ -2,5 +2,5 @@ public interface IDeleteAccessPolicyCommand { - Task DeleteAsync(Guid id); + Task DeleteAsync(Guid id, Guid userId); } diff --git a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/IUpdateAccessPolicyCommand.cs b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/IUpdateAccessPolicyCommand.cs index 225f6a752e..afeb47e9fd 100644 --- a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/IUpdateAccessPolicyCommand.cs +++ b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/IUpdateAccessPolicyCommand.cs @@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; public interface IUpdateAccessPolicyCommand { - public Task UpdateAsync(Guid id, bool read, bool write); + public Task UpdateAsync(Guid id, bool read, bool write, Guid userId); } diff --git a/src/Core/SecretsManager/Entities/AccessPolicy.cs b/src/Core/SecretsManager/Entities/AccessPolicy.cs index 62e79d0f22..bce9de0629 100644 --- a/src/Core/SecretsManager/Entities/AccessPolicy.cs +++ b/src/Core/SecretsManager/Entities/AccessPolicy.cs @@ -24,34 +24,39 @@ public abstract class BaseAccessPolicy public class UserProjectAccessPolicy : BaseAccessPolicy { public Guid? OrganizationUserId { get; set; } - public Guid? GrantedProjectId { get; set; } public User? User { get; set; } + public Guid? GrantedProjectId { get; set; } + public Project? GrantedProject { get; set; } } public class UserServiceAccountAccessPolicy : BaseAccessPolicy { public Guid? OrganizationUserId { get; set; } - public Guid? GrantedServiceAccountId { get; set; } public User? User { get; set; } + public Guid? GrantedServiceAccountId { get; set; } + public ServiceAccount? GrantedServiceAccount { get; set; } } public class GroupProjectAccessPolicy : BaseAccessPolicy { public Guid? GroupId { get; set; } - public Guid? GrantedProjectId { get; set; } public Group? Group { get; set; } + public Guid? GrantedProjectId { get; set; } + public Project? GrantedProject { get; set; } } public class GroupServiceAccountAccessPolicy : BaseAccessPolicy { public Guid? GroupId { get; set; } - public Guid? GrantedServiceAccountId { get; set; } public Group? Group { get; set; } + public Guid? GrantedServiceAccountId { get; set; } + public ServiceAccount? GrantedServiceAccount { get; set; } } public class ServiceAccountProjectAccessPolicy : BaseAccessPolicy { public Guid? ServiceAccountId { get; set; } - public Guid? GrantedProjectId { get; set; } public ServiceAccount? ServiceAccount { get; set; } + public Guid? GrantedProjectId { get; set; } + public Project? GrantedProject { get; set; } } diff --git a/src/Core/SecretsManager/Repositories/IServiceAccountRepository.cs b/src/Core/SecretsManager/Repositories/IServiceAccountRepository.cs index 924a18f0b3..740d597ecf 100644 --- a/src/Core/SecretsManager/Repositories/IServiceAccountRepository.cs +++ b/src/Core/SecretsManager/Repositories/IServiceAccountRepository.cs @@ -11,4 +11,5 @@ public interface IServiceAccountRepository Task ReplaceAsync(ServiceAccount serviceAccount); Task UserHasReadAccessToServiceAccount(Guid id, Guid userId); Task UserHasWriteAccessToServiceAccount(Guid id, Guid userId); + Task> GetManyByOrganizationIdWriteAccessAsync(Guid organizationId, Guid userId, AccessClientType accessType); } diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs index 9d42b67e8b..9d48a8d3a5 100644 --- a/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs @@ -1,9 +1,11 @@ -using System.Net.Http.Headers; +using System.Net; +using System.Net.Http.Headers; using Bit.Api.IntegrationTest.Factories; -using Bit.Api.IntegrationTest.Helpers; +using Bit.Api.IntegrationTest.SecretsManager.Enums; +using Bit.Api.Models.Response; using Bit.Api.SecretsManager.Models.Request; using Bit.Api.SecretsManager.Models.Response; -using Bit.Core.Entities; +using Bit.Core.Enums; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; using Bit.Test.Common.Helpers; @@ -13,16 +15,17 @@ namespace Bit.Api.IntegrationTest.SecretsManager.Controllers; public class AccessPoliciesControllerTest : IClassFixture, IAsyncLifetime { + private const string _mockEncryptedString = + "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98sp4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg="; + private readonly IAccessPolicyRepository _accessPolicyRepository; private readonly HttpClient _client; private readonly ApiApplicationFactory _factory; - - private const string _mockEncryptedString = "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98sp4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg="; - private readonly IProjectRepository _projectRepository; private readonly IServiceAccountRepository _serviceAccountRepository; - private Organization _organization = null!; + private string _email = null!; + private SecretsManagerOrganizationHelper _organizationHelper = null!; public AccessPoliciesControllerTest(ApiApplicationFactory factory) { @@ -35,46 +38,105 @@ public class AccessPoliciesControllerTest : IClassFixture public async Task InitializeAsync() { - var ownerEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com"; - var tokens = await _factory.LoginWithNewAccount(ownerEmail); - var (organization, _) = - await OrganizationTestHelpers.SignUpAsync(_factory, ownerEmail: ownerEmail, billingEmail: ownerEmail); - _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token); - _organization = organization; + _email = $"integration-test{Guid.NewGuid()}@bitwarden.com"; + await _factory.LoginWithNewAccount(_email); + _organizationHelper = new SecretsManagerOrganizationHelper(_factory, _email); } - public Task DisposeAsync() => Task.CompletedTask; - - [Fact] - public async Task CreateProjectAccessPolicies() + public Task DisposeAsync() { - var initialProject = await _projectRepository.CreateAsync(new Project + _client.Dispose(); + return Task.CompletedTask; + } + + private async Task LoginAsync(string email) + { + var tokens = await _factory.LoginAsync(email); + _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task CreateProjectAccessPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + + var project = await _projectRepository.CreateAsync(new Project { - OrganizationId = _organization.Id, - Name = _mockEncryptedString + OrganizationId = org.Id, + Name = _mockEncryptedString, }); - var initialServiceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount { - OrganizationId = _organization.Id, - Name = _mockEncryptedString + OrganizationId = org.Id, + Name = _mockEncryptedString, }); var request = new AccessPoliciesCreateRequest { ServiceAccountAccessPolicyRequests = new List { - new() { GranteeId = initialServiceAccount.Id, Read = true, Write = true } - } + new() { GranteeId = serviceAccount.Id, Read = true, Write = true }, + }, }; - var response = await _client.PostAsJsonAsync($"/projects/{initialProject.Id}/access-policies", request); + var response = await _client.PostAsJsonAsync($"/projects/{project.Id}/access-policies", request); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task CreateProjectAccessPolicies(PermissionType permissionType) + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + + var project = await _projectRepository.CreateAsync(new Project + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + var accessPolicies = new List + { + new UserProjectAccessPolicy + { + GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + var request = new AccessPoliciesCreateRequest + { + ServiceAccountAccessPolicyRequests = new List + { + new() { GranteeId = serviceAccount.Id, Read = true, Write = true }, + }, + }; + + var response = await _client.PostAsJsonAsync($"/projects/{project.Id}/access-policies", request); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync(); Assert.NotNull(result); - Assert.Equal(initialServiceAccount.Id, result!.ServiceAccountAccessPolicies.First().ServiceAccountId); + Assert.Equal(serviceAccount.Id, result!.ServiceAccountAccessPolicies.First().ServiceAccountId); Assert.True(result.ServiceAccountAccessPolicies.First().Read); Assert.True(result.ServiceAccountAccessPolicies.First().Write); AssertHelper.AssertRecent(result.ServiceAccountAccessPolicies.First().RevisionDate); @@ -91,15 +153,103 @@ public class AccessPoliciesControllerTest : IClassFixture } [Fact] - public async Task UpdateAccessPolicy() + public async Task CreateProjectAccessPolicies_NoPermission() { - var initData = await SetupAccessPolicyRequest(); + // Create a new account as a user + var (org, _) = await _organizationHelper.Initialize(true, true); + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var project = await _projectRepository.CreateAsync(new Project + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + var request = new AccessPoliciesCreateRequest + { + ServiceAccountAccessPolicyRequests = new List + { + new() { GranteeId = serviceAccount.Id, Read = true, Write = true }, + }, + }; + + var response = await _client.PostAsJsonAsync($"/projects/{project.Id}/access-policies", request); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task UpdateAccessPolicy_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + var initData = await SetupAccessPolicyRequest(org.Id); const bool expectedRead = true; const bool expectedWrite = false; var request = new AccessPolicyUpdateRequest { Read = expectedRead, Write = expectedWrite }; - var response = await _client.PutAsJsonAsync($"/access-policies/{initData.InitialAccessPolicyId}", request); + var response = await _client.PutAsJsonAsync($"/access-policies/{initData.AccessPolicyId}", request); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task UpdateAccessPolicy_NoPermission() + { + // Create a new account as a user + var (org, _) = await _organizationHelper.Initialize(true, true); + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var initData = await SetupAccessPolicyRequest(orgUser.OrganizationId); + + const bool expectedRead = true; + const bool expectedWrite = false; + var request = new AccessPolicyUpdateRequest { Read = expectedRead, Write = expectedWrite }; + + var response = await _client.PutAsJsonAsync($"/access-policies/{initData.AccessPolicyId}", request); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task UpdateAccessPolicy(PermissionType permissionType) + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + var initData = await SetupAccessPolicyRequest(org.Id); + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + var accessPolicies = new List + { + new UserProjectAccessPolicy + { + GrantedProjectId = initData.ProjectId, OrganizationUserId = orgUser.Id, Read = true, Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + + const bool expectedRead = true; + const bool expectedWrite = false; + var request = new AccessPolicyUpdateRequest { Read = expectedRead, Write = expectedWrite }; + + var response = await _client.PutAsJsonAsync($"/access-policies/{initData.AccessPolicyId}", request); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync(); @@ -116,29 +266,78 @@ public class AccessPoliciesControllerTest : IClassFixture AssertHelper.AssertRecent(updatedAccessPolicy.RevisionDate); } + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task DeleteAccessPolicy_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + var initData = await SetupAccessPolicyRequest(org.Id); + + var response = await _client.DeleteAsync($"/access-policies/{initData.AccessPolicyId}"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } [Fact] - public async Task DeleteAccessPolicy() + public async Task DeleteAccessPolicy_NoPermission() { - var initData = await SetupAccessPolicyRequest(); + // Create a new account as a user + var (org, _) = await _organizationHelper.Initialize(true, true); + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); - var response = await _client.DeleteAsync($"/access-policies/{initData.InitialAccessPolicyId}"); + var initData = await SetupAccessPolicyRequest(orgUser.OrganizationId); + + var response = await _client.DeleteAsync($"/access-policies/{initData.AccessPolicyId}"); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task DeleteAccessPolicy(PermissionType permissionType) + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + var initData = await SetupAccessPolicyRequest(org.Id); + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + var accessPolicies = new List + { + new UserProjectAccessPolicy + { + GrantedProjectId = initData.ProjectId, OrganizationUserId = orgUser.Id, Read = true, Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + + var response = await _client.DeleteAsync($"/access-policies/{initData.AccessPolicyId}"); response.EnsureSuccessStatusCode(); - var test = await _accessPolicyRepository.GetByIdAsync(initData.InitialAccessPolicyId); + var test = await _accessPolicyRepository.GetByIdAsync(initData.AccessPolicyId); Assert.Null(test); } [Fact] public async Task GetProjectAccessPolicies_ReturnsEmpty() { - var initialProject = await _projectRepository.CreateAsync(new Project + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + + var project = await _projectRepository.CreateAsync(new Project { - OrganizationId = _organization.Id, + OrganizationId = org.Id, Name = _mockEncryptedString, }); - var response = await _client.GetAsync($"/projects/{initialProject.Id}/access-policies"); + var response = await _client.GetAsync($"/projects/{project.Id}/access-policies"); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync(); @@ -149,12 +348,59 @@ public class AccessPoliciesControllerTest : IClassFixture Assert.Empty(result!.ServiceAccountAccessPolicies); } - [Fact] - public async Task GetProjectAccessPolicies() + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task GetProjectAccessPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) { - var initData = await SetupAccessPolicyRequest(); + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + var initData = await SetupAccessPolicyRequest(org.Id); - var response = await _client.GetAsync($"/projects/{initData.InitialProjectId}/access-policies"); + var response = await _client.GetAsync($"/projects/{initData.ProjectId}/access-policies"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetProjectAccessPolicies_NoPermission() + { + // Create a new account as a user + var (org, _) = await _organizationHelper.Initialize(true, true); + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var initData = await SetupAccessPolicyRequest(orgUser.OrganizationId); + + var response = await _client.GetAsync($"/projects/{initData.ProjectId}/access-policies"); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task GetProjectAccessPolicies(PermissionType permissionType) + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + var initData = await SetupAccessPolicyRequest(org.Id); + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + var accessPolicies = new List + { + new UserProjectAccessPolicy + { + GrantedProjectId = initData.ProjectId, OrganizationUserId = orgUser.Id, Read = true, Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + + var response = await _client.GetAsync($"/projects/{initData.ProjectId}/access-policies"); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync(); @@ -163,44 +409,166 @@ public class AccessPoliciesControllerTest : IClassFixture Assert.Single(result!.ServiceAccountAccessPolicies); } - private async Task SetupAccessPolicyRequest() + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task GetPeoplePotentialGrantees_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) { - var initialProject = await _projectRepository.CreateAsync(new Project + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + + var response = + await _client.GetAsync( + $"/organizations/{org.Id}/access-policies/people/potential-grantees"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task GetPeoplePotentialGrantees_Success(PermissionType permissionType) + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + + if (permissionType == PermissionType.RunAsUserWithPermission) { - OrganizationId = _organization.Id, + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + } + + var response = + await _client.GetAsync( + $"/organizations/{org.Id}/access-policies/people/potential-grantees"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync>(); + + Assert.NotNull(result?.Data); + Assert.NotEmpty(result!.Data); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task GetServiceAccountPotentialGrantees_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + + var response = + await _client.GetAsync( + $"/organizations/{org.Id}/access-policies/service-accounts/potential-grantees"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetServiceAccountPotentialGrantees_OnlyReturnsServiceAccountsWithWriteAccess() + { + // Create a new account as a user + var (org, _) = await _organizationHelper.Initialize(true, true); + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = org.Id, Name = _mockEncryptedString, }); - var initialServiceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + + var response = + await _client.GetAsync( + $"/organizations/{org.Id}/access-policies/service-accounts/potential-grantees"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync>(); + + Assert.NotNull(result?.Data); + Assert.Empty(result!.Data); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task GetServiceAccountsPotentialGrantees_Success(PermissionType permissionType) + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount { - OrganizationId = _organization.Id, + OrganizationId = org.Id, Name = _mockEncryptedString, }); - var initialAccessPolicy = await _accessPolicyRepository.CreateManyAsync( + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + await _accessPolicyRepository.CreateManyAsync( + new List + { + new UserServiceAccountAccessPolicy + { + GrantedServiceAccountId = serviceAccount.Id, + OrganizationUserId = orgUser.Id, + Read = true, + Write = true, + }, + }); + } + + var response = + await _client.GetAsync( + $"/organizations/{org.Id}/access-policies/service-accounts/potential-grantees"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync>(); + + Assert.NotNull(result?.Data); + Assert.NotEmpty(result!.Data); + Assert.Equal(serviceAccount.Id.ToString(), result!.Data.First(x => x.Id == serviceAccount.Id.ToString()).Id); + } + + private async Task SetupAccessPolicyRequest(Guid organizationId) + { + var project = await _projectRepository.CreateAsync(new Project + { + OrganizationId = organizationId, + Name = _mockEncryptedString, + }); + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = organizationId, + Name = _mockEncryptedString, + }); + + var accessPolicy = await _accessPolicyRepository.CreateManyAsync( new List { new ServiceAccountProjectAccessPolicy { - Read = true, - Write = true, - ServiceAccountId = initialServiceAccount.Id, - GrantedProjectId = initialProject.Id, - } + Read = true, Write = true, ServiceAccountId = serviceAccount.Id, GrantedProjectId = project.Id, + }, }); return new RequestSetupData { - InitialProjectId = initialProject.Id, - InitialServiceAccountId = initialServiceAccount.Id, - InitialAccessPolicyId = initialAccessPolicy.First().Id, + ProjectId = project.Id, + ServiceAccountId = serviceAccount.Id, + AccessPolicyId = accessPolicy.First().Id, }; } private class RequestSetupData { - public Guid InitialProjectId { get; set; } - public Guid InitialAccessPolicyId { get; set; } - public Guid InitialServiceAccountId { get; set; } + public Guid ProjectId { get; set; } + public Guid AccessPolicyId { get; set; } + public Guid ServiceAccountId { get; set; } } } diff --git a/test/Api.IntegrationTest/SecretsManager/Enums/PermissionType.cs b/test/Api.IntegrationTest/SecretsManager/Enums/PermissionType.cs new file mode 100644 index 0000000000..7f1c4d7b99 --- /dev/null +++ b/test/Api.IntegrationTest/SecretsManager/Enums/PermissionType.cs @@ -0,0 +1,7 @@ +namespace Bit.Api.IntegrationTest.SecretsManager.Enums; + +public enum PermissionType +{ + RunAsAdmin, + RunAsUserWithPermission, +} diff --git a/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs index 1a12a5a7e2..34bfce5427 100644 --- a/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs +++ b/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs @@ -1,8 +1,15 @@ using Bit.Api.SecretsManager.Controllers; using Bit.Api.SecretsManager.Models.Request; +using Bit.Core.Context; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Repositories; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Services; +using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; @@ -14,14 +21,64 @@ namespace Bit.Api.Test.SecretsManager.Controllers; [ControllerCustomize(typeof(AccessPoliciesController))] [SutProviderCustomize] +[ProjectCustomize] [JsonDocumentCustomize] public class AccessPoliciesControllerTests { - [Theory] - [BitAutoData] - public async void GetAccessPoliciesByProject_ReturnsEmptyList(SutProvider sutProvider, - Guid id) + public enum PermissionType { + RunAsAdmin, + RunAsUserWithPermission, + } + + private static void SetupAdmin(SutProvider sutProvider, Project data) + { + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + sutProvider.GetDependency().OrganizationAdmin(data.OrganizationId).Returns(true); + } + + private static void SetupUserWithPermission(SutProvider sutProvider, Project data) + { + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + sutProvider.GetDependency().OrganizationAdmin(data.OrganizationId).Returns(false); + sutProvider.GetDependency().OrganizationUser(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().UserHasWriteAccessToProject(default, default) + .ReturnsForAnyArgs(true); + } + + private static void SetupUserWithoutPermission(SutProvider sutProvider, Project data) + { + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + sutProvider.GetDependency().OrganizationAdmin(data.OrganizationId).Returns(false); + sutProvider.GetDependency().OrganizationUser(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().UserHasWriteAccessToProject(default, default) + .ReturnsForAnyArgs(false); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void GetAccessPoliciesByProject_ReturnsEmptyList( + PermissionType permissionType, + SutProvider sutProvider, + Guid id, Project data) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, data); + break; + case PermissionType.RunAsUserWithPermission: + SetupUserWithPermission(sutProvider, data); + break; + } + var result = await sutProvider.Sut.GetProjectAccessPoliciesAsync(id); await sutProvider.GetDependency().Received(1) @@ -34,9 +91,39 @@ public class AccessPoliciesControllerTests [Theory] [BitAutoData] - public async void GetAccessPoliciesByProject_Success(SutProvider sutProvider, Guid id, + public async void GetAccessPoliciesByProject_UserWithoutPermission_Throws( + SutProvider sutProvider, + Guid id, + Project data) + { + SetupUserWithoutPermission(sutProvider, data); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetProjectAccessPoliciesAsync(id)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .GetManyByGrantedProjectIdAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void GetAccessPoliciesByProject_Admin_Success( + PermissionType permissionType, + SutProvider sutProvider, + Guid id, + Project data, UserProjectAccessPolicy resultAccessPolicy) { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, data); + break; + case PermissionType.RunAsUserWithPermission: + SetupUserWithPermission(sutProvider, data); + break; + } + sutProvider.GetDependency().GetManyByGrantedProjectIdAsync(default) .ReturnsForAnyArgs(new List { resultAccessPolicy }); @@ -52,36 +139,244 @@ public class AccessPoliciesControllerTests [Theory] [BitAutoData] - public async void CreateAccessPolicies_Success(SutProvider sutProvider, Guid id, - UserProjectAccessPolicy data, AccessPoliciesCreateRequest request) + public async void GetAccessPoliciesByProject_ProjectsExist_UserWithoutPermission_Throws( + SutProvider sutProvider, + Guid id, + Project data, + UserProjectAccessPolicy resultAccessPolicy) { - sutProvider.GetDependency().CreateAsync(default) - .ReturnsForAnyArgs(new List { data }); - var result = await sutProvider.Sut.CreateProjectAccessPoliciesAsync(id, request); - await sutProvider.GetDependency().Received(1) - .CreateAsync(Arg.Any>()); - } + SetupUserWithoutPermission(sutProvider, data); + sutProvider.GetDependency().GetManyByGrantedProjectIdAsync(default) + .ReturnsForAnyArgs(new List { resultAccessPolicy }); + await Assert.ThrowsAsync(() => sutProvider.Sut.GetProjectAccessPoliciesAsync(id)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .GetManyByGrantedProjectIdAsync(Arg.Any()); + } [Theory] [BitAutoData] - public async void UpdateAccessPolicies_Success(SutProvider sutProvider, Guid id, - UserProjectAccessPolicy data, AccessPolicyUpdateRequest request) + public async void CreateAccessPolicies_Success( + SutProvider sutProvider, + Guid id, + UserProjectAccessPolicy data, + AccessPoliciesCreateRequest request) { - sutProvider.GetDependency().UpdateAsync(default, default, default) + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + sutProvider.GetDependency().CreateForProjectAsync(default, default, default) + .ReturnsForAnyArgs(new List { data }); + + await sutProvider.Sut.CreateProjectAccessPoliciesAsync(id, request); + + await sutProvider.GetDependency().Received(1) + .CreateForProjectAsync(Arg.Any(), Arg.Any>(), Arg.Any()); + } + + [Theory] + [BitAutoData] + public async void UpdateAccessPolicies_Success( + SutProvider sutProvider, + Guid id, + UserProjectAccessPolicy data, + AccessPolicyUpdateRequest request) + { + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + sutProvider.GetDependency().UpdateAsync(default, default, default, default) .ReturnsForAnyArgs(data); - var result = await sutProvider.Sut.UpdateAccessPolicyAsync(id, request); + + await sutProvider.Sut.UpdateAccessPolicyAsync(id, request); + await sutProvider.GetDependency().Received(1) - .UpdateAsync(Arg.Any(), Arg.Is(request.Read), Arg.Is(request.Write)); + .UpdateAsync(Arg.Any(), Arg.Is(request.Read), Arg.Is(request.Write), Arg.Any()); } [Theory] [BitAutoData] public async void DeleteAccessPolicies_Success(SutProvider sutProvider, Guid id) { - sutProvider.GetDependency().DeleteAsync(default).ReturnsNull(); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + sutProvider.GetDependency().DeleteAsync(default, default).ReturnsNull(); + await sutProvider.Sut.DeleteAccessPolicyAsync(id); + await sutProvider.GetDependency().Received(1) - .DeleteAsync(Arg.Any()); + .DeleteAsync(Arg.Any(), Arg.Any()); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void GetPeoplePotentialGranteesAsync_ReturnsEmptyList( + PermissionType permissionType, + SutProvider sutProvider, + Guid id) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + sutProvider.GetDependency().OrganizationAdmin(id).Returns(true); + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + break; + case PermissionType.RunAsUserWithPermission: + sutProvider.GetDependency().OrganizationAdmin(id).Returns(false); + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + break; + } + + var result = await sutProvider.Sut.GetPeoplePotentialGranteesAsync(id); + + await sutProvider.GetDependency().Received(1) + .GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id))); + + await sutProvider.GetDependency().Received(1) + .GetManyDetailsByOrganizationAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id))); + + Assert.Empty(result.Data); + } + + [Theory] + [BitAutoData] + public async void GetPeoplePotentialGranteesAsync_UserWithoutPermission_Throws( + SutProvider sutProvider, + Guid id) + { + sutProvider.GetDependency().OrganizationAdmin(id).Returns(false); + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(false); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetPeoplePotentialGranteesAsync(id)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .GetManyByOrganizationIdAsync(Arg.Any()); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .GetManyDetailsByOrganizationAsync(Arg.Any()); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .GetManyByOrganizationIdWriteAccessAsync(Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void GetPeoplePotentialGranteesAsync_Success( + PermissionType permissionType, + SutProvider sutProvider, + Guid id, + Group mockGroup) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + sutProvider.GetDependency().OrganizationAdmin(id).Returns(true); + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + break; + case PermissionType.RunAsUserWithPermission: + sutProvider.GetDependency().OrganizationAdmin(id).Returns(false); + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + break; + } + + sutProvider.GetDependency().GetManyByOrganizationIdAsync(default) + .ReturnsForAnyArgs(new List { mockGroup }); + + var result = await sutProvider.Sut.GetPeoplePotentialGranteesAsync(id); + + await sutProvider.GetDependency().Received(1) + .GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id))); + + await sutProvider.GetDependency().Received(1) + .GetManyDetailsByOrganizationAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id))); + + Assert.NotEmpty(result.Data); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void GetServiceAccountsPotentialGranteesAsync_ReturnsEmptyList( + PermissionType permissionType, + SutProvider sutProvider, + Guid id) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + sutProvider.GetDependency().OrganizationAdmin(id).Returns(true); + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + break; + case PermissionType.RunAsUserWithPermission: + sutProvider.GetDependency().OrganizationAdmin(id).Returns(false); + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + break; + } + + var result = await sutProvider.Sut.GetServiceAccountsPotentialGranteesAsync(id); + + await sutProvider.GetDependency().Received(1) + .GetManyByOrganizationIdWriteAccessAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), + Arg.Is(AssertHelper.AssertPropertyEqual(id)), + Arg.Any()); + + Assert.Empty(result.Data); + } + + [Theory] + [BitAutoData] + public async void GetServiceAccountsPotentialGranteesAsync_UserWithoutPermission_Throws( + SutProvider sutProvider, + Guid id) + { + sutProvider.GetDependency().OrganizationAdmin(id).Returns(false); + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(false); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetServiceAccountsPotentialGranteesAsync(id)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .GetManyByOrganizationIdWriteAccessAsync(Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void GetServiceAccountsPotentialGranteesAsync_Success( + PermissionType permissionType, + SutProvider sutProvider, + Guid id, + ServiceAccount mockServiceAccount) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + sutProvider.GetDependency().OrganizationAdmin(id).Returns(true); + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + break; + case PermissionType.RunAsUserWithPermission: + sutProvider.GetDependency().OrganizationAdmin(id).Returns(false); + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + break; + } + + sutProvider.GetDependency().GetManyByOrganizationIdWriteAccessAsync(default, default, default) + .ReturnsForAnyArgs(new List { mockServiceAccount }); + + var result = await sutProvider.Sut.GetServiceAccountsPotentialGranteesAsync(id); + + await sutProvider.GetDependency().Received(1) + .GetManyByOrganizationIdWriteAccessAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), + Arg.Is(AssertHelper.AssertPropertyEqual(id)), + Arg.Any()); + + Assert.NotEmpty(result.Data); } } From 1ee14d93e63f4843f35ca573383ad1de577ce8ba Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Tue, 7 Feb 2023 14:30:22 -0600 Subject: [PATCH 07/49] [SM-473] Access Policies - Service Accounts (#2658) * Add service account access policy endpoints * Add unit & integration tests for new endpoints * Fix formatting on response models * Cleanup unit tests --- .../CreateAccessPoliciesCommand.cs | 86 ++++- .../Repositories/AccessPolicyRepository.cs | 246 ++++++------- .../CreateAccessPoliciesCommandTests.cs | 347 ++++++++++++++---- .../Controllers/AccessPoliciesController.cs | 47 ++- .../Request/AccessPoliciesCreateRequest.cs | 69 +++- .../ProjectAccessPoliciesResponseModel.cs | 2 + ...rviceAccountAccessPoliciesResponseModel.cs | 39 ++ .../ICreateAccessPoliciesCommand.cs | 1 + .../Repositories/DatabaseContext.cs | 2 + .../AccessPoliciesControllerTest.cs | 229 ++++++++++++ .../AccessPoliciesControllerTests.cs | 267 +++++++++----- 11 files changed, 1030 insertions(+), 305 deletions(-) create mode 100644 src/Api/SecretsManager/Models/Response/ServiceAccountAccessPoliciesResponseModel.cs diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs index 2b003aae48..ffc39099e4 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs @@ -12,15 +12,18 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand private readonly IAccessPolicyRepository _accessPolicyRepository; private readonly ICurrentContext _currentContext; private readonly IProjectRepository _projectRepository; + private readonly IServiceAccountRepository _serviceAccountRepository; public CreateAccessPoliciesCommand( IAccessPolicyRepository accessPolicyRepository, ICurrentContext currentContext, - IProjectRepository projectRepository) + IProjectRepository projectRepository, + IServiceAccountRepository serviceAccountRepository) { - _projectRepository = projectRepository; _accessPolicyRepository = accessPolicyRepository; _currentContext = currentContext; + _projectRepository = projectRepository; + _serviceAccountRepository = serviceAccountRepository; } public async Task> CreateForProjectAsync(Guid projectId, @@ -32,21 +35,33 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand throw new NotFoundException(); } - var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId); - var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + await CheckPermissionAsync(project.OrganizationId, userId, projectId); + CheckForDistinctAccessPolicies(accessPolicies); + await CheckAccessPoliciesDoNotExistAsync(accessPolicies); - var hasAccess = accessClient switch - { - AccessClientType.NoAccessCheck => true, - AccessClientType.User => await _projectRepository.UserHasWriteAccessToProject(project.Id, userId), - _ => false, - }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + return await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(projectId); + } - if (!hasAccess) + public async Task> CreateForServiceAccountAsync(Guid serviceAccountId, + List accessPolicies, Guid userId) + { + var serviceAccount = await _serviceAccountRepository.GetByIdAsync(serviceAccountId); + if (serviceAccount == null || !_currentContext.AccessSecretsManager(serviceAccount.OrganizationId)) { throw new NotFoundException(); } + await CheckPermissionAsync(serviceAccount.OrganizationId, userId, serviceAccountIdToCheck: serviceAccountId); + CheckForDistinctAccessPolicies(accessPolicies); + await CheckAccessPoliciesDoNotExistAsync(accessPolicies); + + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + return await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(serviceAccountId); + } + + private static void CheckForDistinctAccessPolicies(IReadOnlyCollection accessPolicies) + { var distinctAccessPolicies = accessPolicies.DistinctBy(baseAccessPolicy => { return baseAccessPolicy switch @@ -55,6 +70,9 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand GroupProjectAccessPolicy ap => new Tuple(ap.GroupId, ap.GrantedProjectId), ServiceAccountProjectAccessPolicy ap => new Tuple(ap.ServiceAccountId, ap.GrantedProjectId), + UserServiceAccountAccessPolicy ap => new Tuple(ap.OrganizationUserId, + ap.GrantedServiceAccountId), + GroupServiceAccountAccessPolicy ap => new Tuple(ap.GroupId, ap.GrantedServiceAccountId), _ => throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)), }; }).ToList(); @@ -63,7 +81,10 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand { throw new BadRequestException("Resources must be unique"); } + } + private async Task CheckAccessPoliciesDoNotExistAsync(List accessPolicies) + { foreach (var accessPolicy in accessPolicies) { if (await _accessPolicyRepository.AccessPolicyExists(accessPolicy)) @@ -71,7 +92,46 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand throw new BadRequestException("Resource already exists"); } } - await _accessPolicyRepository.CreateManyAsync(accessPolicies); - return await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(projectId); + } + + private async Task CheckPermissionAsync(Guid organizationId, + Guid userId, + Guid? projectIdToCheck = null, + Guid? serviceAccountIdToCheck = null) + { + var orgAdmin = await _currentContext.OrganizationAdmin(organizationId); + var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + + bool hasAccess; + switch (accessClient) + { + case AccessClientType.NoAccessCheck: + hasAccess = true; + break; + case AccessClientType.User: + if (projectIdToCheck != null) + { + hasAccess = await _projectRepository.UserHasWriteAccessToProject(projectIdToCheck.Value, userId); + } + else if (serviceAccountIdToCheck != null) + { + hasAccess = await _serviceAccountRepository.UserHasWriteAccessToServiceAccount( + serviceAccountIdToCheck.Value, + userId); + } + else + { + hasAccess = false; + } + break; + default: + hasAccess = false; + break; + } + + if (!hasAccess) + { + throw new NotFoundException(); + } } } diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs index b61bdcbfd7..8e5015f8b5 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs @@ -16,152 +16,152 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli public async Task> CreateManyAsync(List baseAccessPolicies) { - using (var scope = ServiceScopeFactory.CreateScope()) + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + foreach (var baseAccessPolicy in baseAccessPolicies) { - var dbContext = GetDatabaseContext(scope); - foreach (var baseAccessPolicy in baseAccessPolicies) - { - baseAccessPolicy.SetNewId(); - switch (baseAccessPolicy) - { - case Core.SecretsManager.Entities.UserProjectAccessPolicy accessPolicy: - { - var entity = - Mapper.Map(accessPolicy); - await dbContext.AddAsync(entity); - break; - } - case Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy: - { - var entity = - Mapper.Map(accessPolicy); - await dbContext.AddAsync(entity); - break; - } - case Core.SecretsManager.Entities.GroupProjectAccessPolicy accessPolicy: - { - var entity = Mapper.Map(accessPolicy); - await dbContext.AddAsync(entity); - break; - } - case Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy: - { - var entity = Mapper.Map(accessPolicy); - await dbContext.AddAsync(entity); - break; - } - case Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy accessPolicy: - { - var entity = Mapper.Map(accessPolicy); - await dbContext.AddAsync(entity); - break; - } - } - } - - await dbContext.SaveChangesAsync(); - return baseAccessPolicies; - } - } - - public async Task AccessPolicyExists(Core.SecretsManager.Entities.BaseAccessPolicy baseAccessPolicy) - { - using (var scope = ServiceScopeFactory.CreateScope()) - { - var dbContext = GetDatabaseContext(scope); + baseAccessPolicy.SetNewId(); switch (baseAccessPolicy) { case Core.SecretsManager.Entities.UserProjectAccessPolicy accessPolicy: { - var policy = await dbContext.UserProjectAccessPolicy - .Where(c => c.OrganizationUserId == accessPolicy.OrganizationUserId && - c.GrantedProjectId == accessPolicy.GrantedProjectId) - .FirstOrDefaultAsync(); - return policy != null; + var entity = + Mapper.Map(accessPolicy); + await dbContext.AddAsync(entity); + break; + } + case Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy: + { + var entity = + Mapper.Map(accessPolicy); + await dbContext.AddAsync(entity); + break; } case Core.SecretsManager.Entities.GroupProjectAccessPolicy accessPolicy: { - var policy = await dbContext.GroupProjectAccessPolicy - .Where(c => c.GroupId == accessPolicy.GroupId && - c.GrantedProjectId == accessPolicy.GrantedProjectId) - .FirstOrDefaultAsync(); - return policy != null; + var entity = Mapper.Map(accessPolicy); + await dbContext.AddAsync(entity); + break; + } + case Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy: + { + var entity = Mapper.Map(accessPolicy); + await dbContext.AddAsync(entity); + break; } case Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy accessPolicy: { - var policy = await dbContext.ServiceAccountProjectAccessPolicy - .Where(c => c.ServiceAccountId == accessPolicy.ServiceAccountId && - c.GrantedProjectId == accessPolicy.GrantedProjectId) - .FirstOrDefaultAsync(); - return policy != null; + var entity = Mapper.Map(accessPolicy); + await dbContext.AddAsync(entity); + break; } - default: - throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)); } } + + await dbContext.SaveChangesAsync(); + return baseAccessPolicies; + } + + public async Task AccessPolicyExists(Core.SecretsManager.Entities.BaseAccessPolicy baseAccessPolicy) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + switch (baseAccessPolicy) + { + case Core.SecretsManager.Entities.UserProjectAccessPolicy accessPolicy: + { + var policy = await dbContext.UserProjectAccessPolicy + .Where(c => c.OrganizationUserId == accessPolicy.OrganizationUserId && + c.GrantedProjectId == accessPolicy.GrantedProjectId) + .FirstOrDefaultAsync(); + return policy != null; + } + case Core.SecretsManager.Entities.GroupProjectAccessPolicy accessPolicy: + { + var policy = await dbContext.GroupProjectAccessPolicy + .Where(c => c.GroupId == accessPolicy.GroupId && + c.GrantedProjectId == accessPolicy.GrantedProjectId) + .FirstOrDefaultAsync(); + return policy != null; + } + case Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy accessPolicy: + { + var policy = await dbContext.ServiceAccountProjectAccessPolicy + .Where(c => c.ServiceAccountId == accessPolicy.ServiceAccountId && + c.GrantedProjectId == accessPolicy.GrantedProjectId) + .FirstOrDefaultAsync(); + return policy != null; + } + case Core.SecretsManager.Entities.UserServiceAccountAccessPolicy accessPolicy: + { + var policy = await dbContext.UserServiceAccountAccessPolicy + .Where(c => c.OrganizationUserId == accessPolicy.OrganizationUserId && + c.GrantedServiceAccountId == accessPolicy.GrantedServiceAccountId) + .FirstOrDefaultAsync(); + return policy != null; + } + case Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy accessPolicy: + { + var policy = await dbContext.GroupServiceAccountAccessPolicy + .Where(c => c.GroupId == accessPolicy.GroupId && + c.GrantedServiceAccountId == accessPolicy.GrantedServiceAccountId) + .FirstOrDefaultAsync(); + return policy != null; + } + default: + throw new ArgumentException("Unsupported access policy type provided.", nameof(baseAccessPolicy)); + } } public async Task GetByIdAsync(Guid id) { - using (var scope = ServiceScopeFactory.CreateScope()) - { - var dbContext = GetDatabaseContext(scope); - var entity = await dbContext.AccessPolicies.Where(ap => ap.Id == id) - .Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User) - .Include(ap => ((UserProjectAccessPolicy)ap).GrantedProject) - .Include(ap => ((GroupProjectAccessPolicy)ap).Group) - .Include(ap => ((GroupProjectAccessPolicy)ap).GrantedProject) - .Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount) - .Include(ap => ((ServiceAccountProjectAccessPolicy)ap).GrantedProject) - .Include(ap => ((UserServiceAccountAccessPolicy)ap).OrganizationUser.User) - .Include(ap => ((UserServiceAccountAccessPolicy)ap).GrantedServiceAccount) - .Include(ap => ((GroupServiceAccountAccessPolicy)ap).Group) - .Include(ap => ((GroupServiceAccountAccessPolicy)ap).GrantedServiceAccount) - .FirstOrDefaultAsync(); + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var entity = await dbContext.AccessPolicies.Where(ap => ap.Id == id) + .Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User) + .Include(ap => ((UserProjectAccessPolicy)ap).GrantedProject) + .Include(ap => ((GroupProjectAccessPolicy)ap).Group) + .Include(ap => ((GroupProjectAccessPolicy)ap).GrantedProject) + .Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount) + .Include(ap => ((ServiceAccountProjectAccessPolicy)ap).GrantedProject) + .Include(ap => ((UserServiceAccountAccessPolicy)ap).OrganizationUser.User) + .Include(ap => ((UserServiceAccountAccessPolicy)ap).GrantedServiceAccount) + .Include(ap => ((GroupServiceAccountAccessPolicy)ap).Group) + .Include(ap => ((GroupServiceAccountAccessPolicy)ap).GrantedServiceAccount) + .FirstOrDefaultAsync(); - if (entity == null) - { - return null; - } - - return MapToCore(entity); - } + return entity == null ? null : MapToCore(entity); } public async Task ReplaceAsync(Core.SecretsManager.Entities.BaseAccessPolicy baseAccessPolicy) { - using (var scope = ServiceScopeFactory.CreateScope()) + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var entity = await dbContext.AccessPolicies.FindAsync(baseAccessPolicy.Id); + if (entity != null) { - var dbContext = GetDatabaseContext(scope); - var entity = await dbContext.AccessPolicies.FindAsync(baseAccessPolicy.Id); - if (entity != null) - { - dbContext.AccessPolicies.Attach(entity); - entity.Write = baseAccessPolicy.Write; - entity.Read = baseAccessPolicy.Read; - entity.RevisionDate = baseAccessPolicy.RevisionDate; - await dbContext.SaveChangesAsync(); - } + dbContext.AccessPolicies.Attach(entity); + entity.Write = baseAccessPolicy.Write; + entity.Read = baseAccessPolicy.Read; + entity.RevisionDate = baseAccessPolicy.RevisionDate; + await dbContext.SaveChangesAsync(); } } public async Task> GetManyByGrantedProjectIdAsync(Guid id) { - using (var scope = ServiceScopeFactory.CreateScope()) - { - var dbContext = GetDatabaseContext(scope); + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); - var entities = await dbContext.AccessPolicies.Where(ap => - ((UserProjectAccessPolicy)ap).GrantedProjectId == id || - ((GroupProjectAccessPolicy)ap).GrantedProjectId == id || - ((ServiceAccountProjectAccessPolicy)ap).GrantedProjectId == id) - .Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User) - .Include(ap => ((GroupProjectAccessPolicy)ap).Group) - .Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount) - .ToListAsync(); - - return entities.Select(MapToCore); - } + var entities = await dbContext.AccessPolicies.Where(ap => + ((UserProjectAccessPolicy)ap).GrantedProjectId == id || + ((GroupProjectAccessPolicy)ap).GrantedProjectId == id || + ((ServiceAccountProjectAccessPolicy)ap).GrantedProjectId == id) + .Include(ap => ((UserProjectAccessPolicy)ap).OrganizationUser.User) + .Include(ap => ((GroupProjectAccessPolicy)ap).Group) + .Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount) + .ToListAsync(); + return entities.Select(MapToCore); } public async Task> GetManyByGrantedServiceAccountIdAsync(Guid id) @@ -181,15 +181,13 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli public async Task DeleteAsync(Guid id) { - using (var scope = ServiceScopeFactory.CreateScope()) + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var entity = await dbContext.AccessPolicies.FindAsync(id); + if (entity != null) { - var dbContext = GetDatabaseContext(scope); - var entity = await dbContext.AccessPolicies.FindAsync(id); - if (entity != null) - { - dbContext.Remove(entity); - await dbContext.SaveChangesAsync(); - } + dbContext.Remove(entity); + await dbContext.SaveChangesAsync(); } } diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs index 451caeffeb..d1c1a29145 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs @@ -17,9 +17,127 @@ namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies; [ProjectCustomize] public class CreateAccessPoliciesCommandTests { + private static List MakeDuplicate(List data, AccessPolicyType accessPolicyType) + { + switch (accessPolicyType) + { + case AccessPolicyType.UserProjectAccessPolicy: + { + var mockAccessPolicy = new UserProjectAccessPolicy + { + OrganizationUserId = Guid.NewGuid(), + GrantedProjectId = Guid.NewGuid(), + }; + data.Add(mockAccessPolicy); + + // Add a duplicate policy + data.Add(mockAccessPolicy); + break; + } + case AccessPolicyType.GroupProjectAccessPolicy: + { + var mockAccessPolicy = new GroupProjectAccessPolicy + { + GroupId = Guid.NewGuid(), + GrantedProjectId = Guid.NewGuid(), + }; + data.Add(mockAccessPolicy); + + // Add a duplicate policy + data.Add(mockAccessPolicy); + break; + } + case AccessPolicyType.ServiceAccountProjectAccessPolicy: + { + var mockAccessPolicy = new ServiceAccountProjectAccessPolicy + { + ServiceAccountId = Guid.NewGuid(), + GrantedProjectId = Guid.NewGuid(), + }; + data.Add(mockAccessPolicy); + + // Add a duplicate policy + data.Add(mockAccessPolicy); + break; + } + case AccessPolicyType.UserServiceAccountAccessPolicy: + { + var mockAccessPolicy = new UserServiceAccountAccessPolicy + { + OrganizationUserId = Guid.NewGuid(), + GrantedServiceAccountId = Guid.NewGuid(), + }; + data.Add(mockAccessPolicy); + + // Add a duplicate policy + data.Add(mockAccessPolicy); + break; + } + case AccessPolicyType.GroupServiceAccountAccessPolicy: + { + var mockAccessPolicy = new GroupServiceAccountAccessPolicy + { + GroupId = Guid.NewGuid(), + GrantedServiceAccountId = Guid.NewGuid(), + }; + data.Add(mockAccessPolicy); + + // Add a duplicate policy + data.Add(mockAccessPolicy); + break; + } + } + + return data; + } + + private static void SetupAdmin(SutProvider sutProvider, Guid organizationId) + { + sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(true); + } + + private static void SetupUser(SutProvider sutProvider, Guid organizationId) + { + sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(false); + } + + private static void SetupPermission(SutProvider sutProvider, + PermissionType permissionType, Project project, Guid userId) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, project.OrganizationId); + break; + case PermissionType.RunAsUserWithPermission: + SetupUser(sutProvider, project.OrganizationId); + sutProvider.GetDependency().UserHasWriteAccessToProject(project.Id, userId) + .Returns(true); + break; + } + } + + private static void SetupPermission(SutProvider sutProvider, + PermissionType permissionType, ServiceAccount serviceAccount, Guid userId) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, serviceAccount.OrganizationId); + break; + case PermissionType.RunAsUserWithPermission: + SetupUser(sutProvider, serviceAccount.OrganizationId); + sutProvider.GetDependency() + .UserHasWriteAccessToServiceAccount(serviceAccount.Id, userId).Returns(true); + break; + } + } + [Theory] [BitAutoData] - public async Task CreateAsync_SmNotEnabled_Throws( + public async Task CreateForProject_SmNotEnabled_Throws( Guid userId, Project project, List userProjectAccessPolicies, @@ -42,7 +160,7 @@ public class CreateAccessPoliciesCommandTests [Theory] [BitAutoData] - public async Task CreateAsync_AlreadyExists_Throws_BadRequestException( + public async Task CreateForProject_AlreadyExists_Throws_BadRequestException( Guid userId, Project project, List userProjectAccessPolicies, @@ -55,10 +173,8 @@ public class CreateAccessPoliciesCommandTests data.AddRange(groupProjectAccessPolicies); data.AddRange(serviceAccountProjectAccessPolicies); - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); + SetupAdmin(sutProvider, project.OrganizationId); sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); - sutProvider.GetDependency().OrganizationAdmin(project.OrganizationId).Returns(true); - sutProvider.GetDependency().AccessPolicyExists(Arg.Any()) .Returns(true); @@ -69,17 +185,13 @@ public class CreateAccessPoliciesCommandTests } [Theory] - [BitAutoData(true, false, false)] - [BitAutoData(false, true, false)] - [BitAutoData(true, true, false)] - [BitAutoData(false, false, true)] - [BitAutoData(true, false, true)] - [BitAutoData(false, true, true)] - [BitAutoData(true, true, true)] - public async Task CreateAsync_NotUnique_ThrowsException( - bool testUserPolicies, - bool testGroupPolicies, - bool testServiceAccountPolicies, + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)] + public async Task CreateForProjectAsync_NotUnique_ThrowsException( + AccessPolicyType accessPolicyType, Guid userId, Project project, List userProjectAccessPolicies, @@ -92,64 +204,24 @@ public class CreateAccessPoliciesCommandTests data.AddRange(userProjectAccessPolicies); data.AddRange(groupProjectAccessPolicies); data.AddRange(serviceAccountProjectAccessPolicies); + data = MakeDuplicate(data, accessPolicyType); - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); + SetupAdmin(sutProvider, project.OrganizationId); sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); - sutProvider.GetDependency().OrganizationAdmin(project.OrganizationId).Returns(true); - - if (testUserPolicies) - { - var mockUserPolicy = new UserProjectAccessPolicy - { - OrganizationUserId = Guid.NewGuid(), - GrantedProjectId = Guid.NewGuid(), - }; - data.Add(mockUserPolicy); - - // Add a duplicate policy - data.Add(mockUserPolicy); - } - - if (testGroupPolicies) - { - var mockGroupPolicy = new GroupProjectAccessPolicy - { - GroupId = Guid.NewGuid(), - GrantedProjectId = Guid.NewGuid(), - }; - data.Add(mockGroupPolicy); - - // Add a duplicate policy - data.Add(mockGroupPolicy); - } - - if (testServiceAccountPolicies) - { - var mockServiceAccountPolicy = new ServiceAccountProjectAccessPolicy - { - ServiceAccountId = Guid.NewGuid(), - GrantedProjectId = Guid.NewGuid(), - }; - data.Add(mockServiceAccountPolicy); - - // Add a duplicate policy - data.Add(mockServiceAccountPolicy); - } - - sutProvider.GetDependency().AccessPolicyExists(Arg.Any()) .Returns(true); await Assert.ThrowsAsync(() => sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId)); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateManyAsync(default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); } [Theory] [BitAutoData(PermissionType.RunAsAdmin)] [BitAutoData(PermissionType.RunAsUserWithPermission)] - public async Task CreateAsync_Success( + public async Task CreateForProject_Success( PermissionType permissionType, Guid userId, Project project, @@ -163,18 +235,8 @@ public class CreateAccessPoliciesCommandTests data.AddRange(groupProjectAccessPolicies); data.AddRange(serviceAccountProjectAccessPolicies); - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); - - switch (permissionType) - { - case PermissionType.RunAsAdmin: - sutProvider.GetDependency().OrganizationAdmin(project.OrganizationId).Returns(true); - break; - case PermissionType.RunAsUserWithPermission: - sutProvider.GetDependency().UserHasWriteAccessToProject(project.Id, userId).Returns(true); - break; - } + SetupPermission(sutProvider, permissionType, project, userId); await sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId); @@ -184,7 +246,7 @@ public class CreateAccessPoliciesCommandTests [Theory] [BitAutoData] - public async Task CreateAsync_User_NoPermission( + public async Task CreateForProject_UserNoPermission_ThrowsNotFound( Guid userId, Project project, List userProjectAccessPolicies, @@ -197,13 +259,148 @@ public class CreateAccessPoliciesCommandTests data.AddRange(groupProjectAccessPolicies); data.AddRange(serviceAccountProjectAccessPolicies); - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(true); - sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); + SetupUser(sutProvider, project.OrganizationId); sutProvider.GetDependency().UserHasWriteAccessToProject(project.Id, userId).Returns(false); + sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); await Assert.ThrowsAsync(() => sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateManyAsync(default); } + + [Theory] + [BitAutoData] + public async Task CreateForServiceAccount_SmNotEnabled_Throws( + Guid userId, + ServiceAccount serviceAccount, + List userProjectAccessPolicies, + List groupProjectAccessPolicies, + List serviceAccountProjectAccessPolicies, + SutProvider sutProvider) + { + var data = new List(); + data.AddRange(userProjectAccessPolicies); + data.AddRange(groupProjectAccessPolicies); + data.AddRange(serviceAccountProjectAccessPolicies); + + sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(false); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateForServiceAccountAsync(serviceAccount.Id, data, userId)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); + } + + [Theory] + [BitAutoData] + public async Task CreateForServiceAccount_AlreadyExists_ThrowsBadRequestException( + Guid userId, + ServiceAccount serviceAccount, + List userProjectAccessPolicies, + List groupProjectAccessPolicies, + List serviceAccountProjectAccessPolicies, + SutProvider sutProvider) + { + var data = new List(); + data.AddRange(userProjectAccessPolicies); + data.AddRange(groupProjectAccessPolicies); + data.AddRange(serviceAccountProjectAccessPolicies); + + SetupAdmin(sutProvider, serviceAccount.OrganizationId); + sutProvider.GetDependency().GetByIdAsync(serviceAccount.Id).Returns(serviceAccount); + sutProvider.GetDependency().AccessPolicyExists(Arg.Any()) + .Returns(true); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateForServiceAccountAsync(serviceAccount.Id, data, userId)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); + } + + [Theory] + [BitAutoData(AccessPolicyType.UserProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)] + [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)] + [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)] + public async Task CreateForServiceAccount_NotUnique_Throws( + AccessPolicyType accessPolicyType, + Guid userId, + ServiceAccount serviceAccount, + List userProjectAccessPolicies, + List groupProjectAccessPolicies, + List serviceAccountProjectAccessPolicies, + SutProvider sutProvider + ) + { + var data = new List(); + data.AddRange(userProjectAccessPolicies); + data.AddRange(groupProjectAccessPolicies); + data.AddRange(serviceAccountProjectAccessPolicies); + data = MakeDuplicate(data, accessPolicyType); + + SetupAdmin(sutProvider, serviceAccount.OrganizationId); + sutProvider.GetDependency().GetByIdAsync(serviceAccount.Id).Returns(serviceAccount); + sutProvider.GetDependency().AccessPolicyExists(Arg.Any()) + .Returns(true); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateForServiceAccountAsync(serviceAccount.Id, data, userId)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); + } + + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async Task CreateForServiceAccount_Success( + PermissionType permissionType, + Guid userId, + ServiceAccount serviceAccount, + List userServiceAccountAccessPolicies, + List groupServiceAccountAccessPolicies, + SutProvider sutProvider) + { + var data = new List(); + data.AddRange(userServiceAccountAccessPolicies); + data.AddRange(groupServiceAccountAccessPolicies); + + sutProvider.GetDependency().GetByIdAsync(serviceAccount.Id).Returns(serviceAccount); + SetupPermission(sutProvider, permissionType, serviceAccount, userId); + + await sutProvider.Sut.CreateForServiceAccountAsync(serviceAccount.Id, data, userId); + + await sutProvider.GetDependency().Received(1) + .CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data))); + } + + [Theory] + [BitAutoData] + public async Task CreateForServiceAccount_UserWithoutPermission_ThrowsNotFound( + Guid userId, + ServiceAccount serviceAccount, + List userServiceAccountAccessPolicies, + List groupServiceAccountAccessPolicies, + SutProvider sutProvider) + { + var data = new List(); + data.AddRange(userServiceAccountAccessPolicies); + data.AddRange(groupServiceAccountAccessPolicies); + + SetupUser(sutProvider, serviceAccount.OrganizationId); + sutProvider.GetDependency().GetByIdAsync(serviceAccount.Id).Returns(serviceAccount); + sutProvider.GetDependency() + .UserHasWriteAccessToServiceAccount(serviceAccount.Id, userId).Returns(false); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateForServiceAccountAsync(serviceAccount.Id, data, userId)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>()); + } } diff --git a/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs b/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs index 254dd09e24..1707fd33c2 100644 --- a/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs +++ b/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs @@ -20,13 +20,13 @@ namespace Bit.Api.SecretsManager.Controllers; public class AccessPoliciesController : Controller { private readonly IAccessPolicyRepository _accessPolicyRepository; + private readonly IServiceAccountRepository _serviceAccountRepository; private readonly ICreateAccessPoliciesCommand _createAccessPoliciesCommand; private readonly ICurrentContext _currentContext; private readonly IDeleteAccessPolicyCommand _deleteAccessPolicyCommand; private readonly IGroupRepository _groupRepository; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IProjectRepository _projectRepository; - private readonly IServiceAccountRepository _serviceAccountRepository; private readonly IUpdateAccessPolicyCommand _updateAccessPolicyCommand; private readonly IUserService _userService; @@ -74,6 +74,26 @@ public class AccessPoliciesController : Controller return new ProjectAccessPoliciesResponseModel(results); } + [HttpPost("/service-accounts/{id}/access-policies")] + public async Task CreateServiceAccountAccessPoliciesAsync([FromRoute] Guid id, + [FromBody] AccessPoliciesCreateRequest request) + { + var userId = _userService.GetProperUserId(User).Value; + var policies = request.ToBaseAccessPoliciesForServiceAccount(id); + var results = await _createAccessPoliciesCommand.CreateForServiceAccountAsync(id, policies, userId); + return new ServiceAccountAccessPoliciesResponseModel(results); + } + + [HttpGet("/service-accounts/{id}/access-policies")] + public async Task GetServiceAccountAccessPoliciesAsync([FromRoute] Guid id) + { + var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id); + await CheckUserHasWriteAccessToServiceAccountAsync(serviceAccount); + + var results = await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(id); + return new ServiceAccountAccessPoliciesResponseModel(results); + } + [HttpPut("{id}")] public async Task UpdateAccessPolicyAsync([FromRoute] Guid id, [FromBody] AccessPolicyUpdateRequest request) @@ -84,7 +104,9 @@ public class AccessPoliciesController : Controller return result switch { UserProjectAccessPolicy accessPolicy => new UserProjectAccessPolicyResponseModel(accessPolicy), + UserServiceAccountAccessPolicy accessPolicy => new UserServiceAccountAccessPolicyResponseModel(accessPolicy), GroupProjectAccessPolicy accessPolicy => new GroupProjectAccessPolicyResponseModel(accessPolicy), + GroupServiceAccountAccessPolicy accessPolicy => new GroupServiceAccountAccessPolicyResponseModel(accessPolicy), ServiceAccountProjectAccessPolicy accessPolicy => new ServiceAccountProjectAccessPolicyResponseModel( accessPolicy), _ => throw new ArgumentException("Unsupported access policy type provided."), @@ -163,4 +185,27 @@ public class AccessPoliciesController : Controller throw new NotFoundException(); } } + + private async Task CheckUserHasWriteAccessToServiceAccountAsync(ServiceAccount serviceAccount) + { + if (serviceAccount == null || !_currentContext.AccessSecretsManager(serviceAccount.OrganizationId)) + { + throw new NotFoundException(); + } + var userId = _userService.GetProperUserId(User).Value; + var orgAdmin = await _currentContext.OrganizationAdmin(serviceAccount.OrganizationId); + var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + + var hasAccess = accessClient switch + { + AccessClientType.NoAccessCheck => true, + AccessClientType.User => await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(serviceAccount.Id, userId), + _ => false, + }; + + if (!hasAccess) + { + throw new NotFoundException(); + } + } } diff --git a/src/Api/SecretsManager/Models/Request/AccessPoliciesCreateRequest.cs b/src/Api/SecretsManager/Models/Request/AccessPoliciesCreateRequest.cs index eab1c1dbaf..689a6c59e1 100644 --- a/src/Api/SecretsManager/Models/Request/AccessPoliciesCreateRequest.cs +++ b/src/Api/SecretsManager/Models/Request/AccessPoliciesCreateRequest.cs @@ -13,7 +13,7 @@ public class AccessPoliciesCreateRequest public IEnumerable? ServiceAccountAccessPolicyRequests { get; set; } - public List ToBaseAccessPoliciesForProject(Guid projectId) + public List ToBaseAccessPoliciesForProject(Guid grantedProjectId) { if (UserAccessPolicyRequests == null && GroupAccessPolicyRequests == null && ServiceAccountAccessPolicyRequests == null) { @@ -21,18 +21,55 @@ public class AccessPoliciesCreateRequest } var userAccessPolicies = UserAccessPolicyRequests? - .Select(x => x.ToUserProjectAccessPolicy(projectId)).ToList(); + .Select(x => x.ToUserProjectAccessPolicy(grantedProjectId)).ToList(); var groupAccessPolicies = GroupAccessPolicyRequests? - .Select(x => x.ToGroupProjectAccessPolicy(projectId)).ToList(); + .Select(x => x.ToGroupProjectAccessPolicy(grantedProjectId)).ToList(); var serviceAccountAccessPolicies = ServiceAccountAccessPolicyRequests? - .Select(x => x.ToServiceAccountProjectAccessPolicy(projectId)).ToList(); + .Select(x => x.ToServiceAccountProjectAccessPolicy(grantedProjectId)).ToList(); var policies = new List(); - if (userAccessPolicies != null) { policies.AddRange(userAccessPolicies); } - if (groupAccessPolicies != null) { policies.AddRange(groupAccessPolicies); } - if (serviceAccountAccessPolicies != null) { policies.AddRange(serviceAccountAccessPolicies); } + if (userAccessPolicies != null) + { + policies.AddRange(userAccessPolicies); + } + + if (groupAccessPolicies != null) + { + policies.AddRange(groupAccessPolicies); + } + + if (serviceAccountAccessPolicies != null) + { + policies.AddRange(serviceAccountAccessPolicies); + } + return policies; + } + + public List ToBaseAccessPoliciesForServiceAccount(Guid grantedServiceAccountId) + { + if (UserAccessPolicyRequests == null && GroupAccessPolicyRequests == null) + { + throw new BadRequestException("No creation requests provided."); + } + + var userAccessPolicies = UserAccessPolicyRequests? + .Select(x => x.ToUserServiceAccountAccessPolicy(grantedServiceAccountId)).ToList(); + + var groupAccessPolicies = GroupAccessPolicyRequests? + .Select(x => x.ToGroupServiceAccountAccessPolicy(grantedServiceAccountId)).ToList(); + + var policies = new List(); + if (userAccessPolicies != null) + { + policies.AddRange(userAccessPolicies); + } + + if (groupAccessPolicies != null) + { + policies.AddRange(groupAccessPolicies); + } return policies; } } @@ -74,4 +111,22 @@ public class AccessPolicyRequest Read = Read, Write = Write }; + + public UserServiceAccountAccessPolicy ToUserServiceAccountAccessPolicy(Guid id) => + new() + { + OrganizationUserId = GranteeId, + GrantedServiceAccountId = id, + Read = Read, + Write = Write + }; + + public GroupServiceAccountAccessPolicy ToGroupServiceAccountAccessPolicy(Guid id) => + new() + { + GroupId = GranteeId, + GrantedServiceAccountId = id, + Read = Read, + Write = Write + }; } diff --git a/src/Api/SecretsManager/Models/Response/ProjectAccessPoliciesResponseModel.cs b/src/Api/SecretsManager/Models/Response/ProjectAccessPoliciesResponseModel.cs index f03411cd39..7308d1833c 100644 --- a/src/Api/SecretsManager/Models/Response/ProjectAccessPoliciesResponseModel.cs +++ b/src/Api/SecretsManager/Models/Response/ProjectAccessPoliciesResponseModel.cs @@ -11,6 +11,7 @@ public class ProjectAccessPoliciesResponseModel : ResponseModel : base(_objectName) { foreach (var baseAccessPolicy in baseAccessPolicies) + { switch (baseAccessPolicy) { case UserProjectAccessPolicy accessPolicy: @@ -24,6 +25,7 @@ public class ProjectAccessPoliciesResponseModel : ResponseModel new ServiceAccountProjectAccessPolicyResponseModel(accessPolicy)); break; } + } } public ProjectAccessPoliciesResponseModel() : base(_objectName) diff --git a/src/Api/SecretsManager/Models/Response/ServiceAccountAccessPoliciesResponseModel.cs b/src/Api/SecretsManager/Models/Response/ServiceAccountAccessPoliciesResponseModel.cs new file mode 100644 index 0000000000..6f047cf881 --- /dev/null +++ b/src/Api/SecretsManager/Models/Response/ServiceAccountAccessPoliciesResponseModel.cs @@ -0,0 +1,39 @@ +using Bit.Core.Models.Api; +using Bit.Core.SecretsManager.Entities; + +namespace Bit.Api.SecretsManager.Models.Response; + +public class ServiceAccountAccessPoliciesResponseModel : ResponseModel +{ + private const string _objectName = "serviceAccountAccessPolicies"; + + public ServiceAccountAccessPoliciesResponseModel(IEnumerable baseAccessPolicies) + : base(_objectName) + { + if (baseAccessPolicies == null) + { + return; + } + + foreach (var baseAccessPolicy in baseAccessPolicies) + { + switch (baseAccessPolicy) + { + case UserServiceAccountAccessPolicy accessPolicy: + UserAccessPolicies.Add(new UserServiceAccountAccessPolicyResponseModel(accessPolicy)); + break; + case GroupServiceAccountAccessPolicy accessPolicy: + GroupAccessPolicies.Add(new GroupServiceAccountAccessPolicyResponseModel(accessPolicy)); + break; + } + } + } + + public ServiceAccountAccessPoliciesResponseModel() : base(_objectName) + { + } + + public List UserAccessPolicies { get; set; } = new(); + + public List GroupAccessPolicies { get; set; } = new(); +} diff --git a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs index 29aa34c0f7..b1b0bf563f 100644 --- a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs +++ b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs @@ -5,4 +5,5 @@ namespace Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; public interface ICreateAccessPoliciesCommand { Task> CreateForProjectAsync(Guid projectId, List accessPolicies, Guid userId); + Task> CreateForServiceAccountAsync(Guid serviceAccountId, List accessPolicies, Guid userId); } diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs index 4368c97b4f..9aeb18dc15 100644 --- a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs +++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs @@ -21,6 +21,8 @@ public class DatabaseContext : DbContext public DbSet UserProjectAccessPolicy { get; set; } public DbSet GroupProjectAccessPolicy { get; set; } public DbSet ServiceAccountProjectAccessPolicy { get; set; } + public DbSet UserServiceAccountAccessPolicy { get; set; } + public DbSet GroupServiceAccountAccessPolicy { get; set; } public DbSet ApiKeys { get; set; } public DbSet Ciphers { get; set; } public DbSet Collections { get; set; } diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs index 9d48a8d3a5..33cd4d6e52 100644 --- a/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs @@ -534,6 +534,235 @@ public class AccessPoliciesControllerTest : IClassFixture Assert.Equal(serviceAccount.Id.ToString(), result!.Data.First(x => x.Id == serviceAccount.Id.ToString()).Id); } + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task CreateServiceAccountAccessPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) + { + var (org, orgUser) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + var request = new AccessPoliciesCreateRequest + { + UserAccessPolicyRequests = new List + { + new() { GranteeId = orgUser.Id, Read = true, Write = true }, + }, + }; + + var response = + await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies", request); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task CreateServiceAccountAccessPolicies(PermissionType permissionType) + { + var (org, orgUser) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + var ownerOrgUserId = orgUser.Id; + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + var request = new AccessPoliciesCreateRequest + { + UserAccessPolicyRequests = new List + { + new() { GranteeId = orgUser.Id, Read = true, Write = true }, + }, + }; + + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, newOrgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + var accessPolicies = new List + { + new UserServiceAccountAccessPolicy + { + GrantedServiceAccountId = serviceAccount.Id, + OrganizationUserId = newOrgUser.Id, + Read = true, + Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + + request = new AccessPoliciesCreateRequest + { + UserAccessPolicyRequests = new List + { + new() { GranteeId = ownerOrgUserId, Read = true, Write = true }, + }, + }; + } + + + var response = + await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies", request); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(); + + Assert.NotNull(result); + Assert.Equal(ownerOrgUserId, + result!.UserAccessPolicies.First(ap => ap.OrganizationUserId == ownerOrgUserId).OrganizationUserId); + Assert.True(result.UserAccessPolicies.First().Read); + Assert.True(result.UserAccessPolicies.First().Write); + AssertHelper.AssertRecent(result.UserAccessPolicies.First().RevisionDate); + AssertHelper.AssertRecent(result.UserAccessPolicies.First().CreationDate); + + var createdAccessPolicy = + await _accessPolicyRepository.GetByIdAsync(result.UserAccessPolicies.First().Id); + Assert.NotNull(createdAccessPolicy); + Assert.Equal(result.UserAccessPolicies.First().Read, createdAccessPolicy!.Read); + Assert.Equal(result.UserAccessPolicies.First().Write, createdAccessPolicy.Write); + Assert.Equal(result.UserAccessPolicies.First().Id, createdAccessPolicy.Id); + AssertHelper.AssertRecent(createdAccessPolicy.CreationDate); + AssertHelper.AssertRecent(createdAccessPolicy.RevisionDate); + } + + [Fact] + public async Task CreateServiceAccountAccessPolicies_NoPermission() + { + // Create a new account as a user + var (org, _) = await _organizationHelper.Initialize(true, true); + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + var request = new AccessPoliciesCreateRequest + { + UserAccessPolicyRequests = new List + { + new() { GranteeId = orgUser.Id, Read = true, Write = true }, + }, + }; + + var response = + await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-policies", request); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task GetServiceAccountAccessPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + var initData = await SetupAccessPolicyRequest(org.Id); + + var response = await _client.GetAsync($"/service-accounts/{initData.ServiceAccountId}/access-policies"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetServiceAccountAccessPolicies_ReturnsEmpty() + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + var response = await _client.GetAsync($"/service-accounts/{serviceAccount.Id}/access-policies"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(); + + Assert.NotNull(result); + Assert.Empty(result!.UserAccessPolicies); + Assert.Empty(result!.GroupAccessPolicies); + } + + [Fact] + public async Task GetServiceAccountAccessPolicies_NoPermission() + { + // Create a new account as a user + var (org, _) = await _organizationHelper.Initialize(true, true); + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var initData = await SetupAccessPolicyRequest(orgUser.OrganizationId); + + var response = await _client.GetAsync($"/service-accounts/{initData.ServiceAccountId}/access-policies"); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task GetServiceAccountAccessPolicies(PermissionType permissionType) + { + var (org, owerOrgUser) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + var initData = await SetupAccessPolicyRequest(org.Id); + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + var accessPolicies = new List + { + new UserServiceAccountAccessPolicy + { + GrantedServiceAccountId = initData.ServiceAccountId, + OrganizationUserId = orgUser.Id, + Read = true, + Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + + var policies = new List + { + new UserServiceAccountAccessPolicy + { + GrantedServiceAccountId = initData.ServiceAccountId, + OrganizationUserId = owerOrgUser.Id, + Read = true, + Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(policies); + + var response = await _client.GetAsync($"/service-accounts/{initData.ServiceAccountId}/access-policies"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync(); + + Assert.NotNull(result?.UserAccessPolicies); + Assert.NotEmpty(result!.UserAccessPolicies); + Assert.Equal(owerOrgUser.Id, + result.UserAccessPolicies.First(x => x.OrganizationUserId == owerOrgUser.Id).OrganizationUserId); + } + private async Task SetupAccessPolicyRequest(Guid organizationId) { var project = await _projectRepository.CreateAsync(new Project diff --git a/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs index 34bfce5427..14b4774677 100644 --- a/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs +++ b/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs @@ -31,51 +31,63 @@ public class AccessPoliciesControllerTests RunAsUserWithPermission, } - private static void SetupAdmin(SutProvider sutProvider, Project data) + private static void SetupAdmin(SutProvider sutProvider, Guid organizationId) { sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - sutProvider.GetDependency().OrganizationAdmin(data.OrganizationId).Returns(true); + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(true); } - private static void SetupUserWithPermission(SutProvider sutProvider, Project data) + private static void SetupUserWithPermission(SutProvider sutProvider, Guid organizationId) { sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - sutProvider.GetDependency().OrganizationAdmin(data.OrganizationId).Returns(false); + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(false); sutProvider.GetDependency().OrganizationUser(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().UserHasWriteAccessToProject(default, default) - .ReturnsForAnyArgs(true); } - private static void SetupUserWithoutPermission(SutProvider sutProvider, Project data) + private static void SetupUserWithoutPermission(SutProvider sutProvider, + Guid organizationId) { sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - sutProvider.GetDependency().OrganizationAdmin(data.OrganizationId).Returns(false); + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(false); sutProvider.GetDependency().OrganizationUser(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().UserHasWriteAccessToProject(default, default) - .ReturnsForAnyArgs(false); + } + + private static void SetupPermission(SutProvider sutProvider, PermissionType permissionType, Guid orgId) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, orgId); + break; + case PermissionType.RunAsUserWithPermission: + SetupUserWithPermission(sutProvider, orgId); + break; + } + } [Theory] [BitAutoData(PermissionType.RunAsAdmin)] [BitAutoData(PermissionType.RunAsUserWithPermission)] - public async void GetAccessPoliciesByProject_ReturnsEmptyList( + public async void GetProjectAccessPolicies_ReturnsEmptyList( PermissionType permissionType, SutProvider sutProvider, Guid id, Project data) { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + switch (permissionType) { case PermissionType.RunAsAdmin: - SetupAdmin(sutProvider, data); + SetupAdmin(sutProvider, data.OrganizationId); break; case PermissionType.RunAsUserWithPermission: - SetupUserWithPermission(sutProvider, data); + SetupUserWithPermission(sutProvider, data.OrganizationId); + sutProvider.GetDependency().UserHasWriteAccessToProject(default, default) + .ReturnsForAnyArgs(true); break; } @@ -91,12 +103,15 @@ public class AccessPoliciesControllerTests [Theory] [BitAutoData] - public async void GetAccessPoliciesByProject_UserWithoutPermission_Throws( + public async void GetProjectAccessPolicies_UserWithoutPermission_Throws( SutProvider sutProvider, Guid id, Project data) { - SetupUserWithoutPermission(sutProvider, data); + SetupUserWithoutPermission(sutProvider, data.OrganizationId); + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + sutProvider.GetDependency().UserHasWriteAccessToProject(default, default) + .ReturnsForAnyArgs(false); await Assert.ThrowsAsync(() => sutProvider.Sut.GetProjectAccessPoliciesAsync(id)); @@ -107,20 +122,23 @@ public class AccessPoliciesControllerTests [Theory] [BitAutoData(PermissionType.RunAsAdmin)] [BitAutoData(PermissionType.RunAsUserWithPermission)] - public async void GetAccessPoliciesByProject_Admin_Success( + public async void GetProjectAccessPolicies_Success( PermissionType permissionType, SutProvider sutProvider, Guid id, Project data, UserProjectAccessPolicy resultAccessPolicy) { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); switch (permissionType) { case PermissionType.RunAsAdmin: - SetupAdmin(sutProvider, data); + SetupAdmin(sutProvider, data.OrganizationId); break; case PermissionType.RunAsUserWithPermission: - SetupUserWithPermission(sutProvider, data); + SetupUserWithPermission(sutProvider, data.OrganizationId); + sutProvider.GetDependency().UserHasWriteAccessToProject(default, default) + .ReturnsForAnyArgs(true); break; } @@ -139,13 +157,17 @@ public class AccessPoliciesControllerTests [Theory] [BitAutoData] - public async void GetAccessPoliciesByProject_ProjectsExist_UserWithoutPermission_Throws( + public async void GetProjectAccessPolicies_ProjectsExist_UserWithoutPermission_Throws( SutProvider sutProvider, Guid id, Project data, UserProjectAccessPolicy resultAccessPolicy) { - SetupUserWithoutPermission(sutProvider, data); + SetupUserWithoutPermission(sutProvider, data.OrganizationId); + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + sutProvider.GetDependency().UserHasWriteAccessToProject(default, default) + .ReturnsForAnyArgs(false); + sutProvider.GetDependency().GetManyByGrantedProjectIdAsync(default) .ReturnsForAnyArgs(new List { resultAccessPolicy }); @@ -155,9 +177,117 @@ public class AccessPoliciesControllerTests .GetManyByGrantedProjectIdAsync(Arg.Any()); } + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void GetServiceAccountAccessPolicies_ReturnsEmptyList( + PermissionType permissionType, + SutProvider sutProvider, + Guid id, ServiceAccount data) + { + sutProvider.GetDependency().GetByIdAsync(data.Id).ReturnsForAnyArgs(data); + + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, data.OrganizationId); + break; + case PermissionType.RunAsUserWithPermission: + SetupUserWithPermission(sutProvider, data.OrganizationId); + sutProvider.GetDependency() + .UserHasWriteAccessToServiceAccount(default, default) + .ReturnsForAnyArgs(true); + break; + } + + var result = await sutProvider.Sut.GetServiceAccountAccessPoliciesAsync(id); + + await sutProvider.GetDependency().Received(1) + .GetManyByGrantedServiceAccountIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id))); + + Assert.Empty(result.UserAccessPolicies); + Assert.Empty(result.GroupAccessPolicies); + } + [Theory] [BitAutoData] - public async void CreateAccessPolicies_Success( + public async void GetServiceAccountAccessPolicies_UserWithoutPermission_Throws( + SutProvider sutProvider, + Guid id, + ServiceAccount data) + { + SetupUserWithoutPermission(sutProvider, data.OrganizationId); + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + sutProvider.GetDependency().UserHasWriteAccessToServiceAccount(default, default) + .ReturnsForAnyArgs(false); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetServiceAccountAccessPoliciesAsync(id)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .GetManyByGrantedServiceAccountIdAsync(Arg.Any()); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void GetServiceAccountAccessPolicies_Success( + PermissionType permissionType, + SutProvider sutProvider, + Guid id, + ServiceAccount data, + UserServiceAccountAccessPolicy resultAccessPolicy) + { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, data.OrganizationId); + break; + case PermissionType.RunAsUserWithPermission: + SetupUserWithPermission(sutProvider, data.OrganizationId); + sutProvider.GetDependency() + .UserHasWriteAccessToServiceAccount(default, default) + .ReturnsForAnyArgs(true); + break; + } + + sutProvider.GetDependency().GetManyByGrantedServiceAccountIdAsync(default) + .ReturnsForAnyArgs(new List { resultAccessPolicy }); + + var result = await sutProvider.Sut.GetServiceAccountAccessPoliciesAsync(id); + + await sutProvider.GetDependency().Received(1) + .GetManyByGrantedServiceAccountIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id))); + + Assert.Empty(result.GroupAccessPolicies); + Assert.NotEmpty(result.UserAccessPolicies); + } + + [Theory] + [BitAutoData] + public async void GetServiceAccountAccessPolicies_ServiceAccountExists_UserWithoutPermission_Throws( + SutProvider sutProvider, + Guid id, + ServiceAccount data, + UserServiceAccountAccessPolicy resultAccessPolicy) + { + SetupUserWithoutPermission(sutProvider, data.OrganizationId); + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + sutProvider.GetDependency().UserHasWriteAccessToServiceAccount(default, default) + .ReturnsForAnyArgs(false); + + sutProvider.GetDependency().GetManyByGrantedServiceAccountIdAsync(default) + .ReturnsForAnyArgs(new List { resultAccessPolicy }); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetServiceAccountAccessPoliciesAsync(id)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .GetManyByGrantedServiceAccountIdAsync(Arg.Any()); + } + + [Theory] + [BitAutoData] + public async void CreateProjectAccessPolicies_Success( SutProvider sutProvider, Guid id, UserProjectAccessPolicy data, @@ -173,6 +303,25 @@ public class AccessPoliciesControllerTests .CreateForProjectAsync(Arg.Any(), Arg.Any>(), Arg.Any()); } + [Theory] + [BitAutoData] + public async void CreateServiceAccountAccessPolicies_Success( + SutProvider sutProvider, + Guid id, + UserServiceAccountAccessPolicy data, + AccessPoliciesCreateRequest request) + { + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + sutProvider.GetDependency() + .CreateForServiceAccountAsync(default, default, default) + .ReturnsForAnyArgs(new List { data }); + + await sutProvider.Sut.CreateServiceAccountAccessPoliciesAsync(id, request); + + await sutProvider.GetDependency().Received(1) + .CreateForServiceAccountAsync(Arg.Any(), Arg.Any>(), Arg.Any()); + } + [Theory] [BitAutoData] public async void UpdateAccessPolicies_Success( @@ -207,25 +356,12 @@ public class AccessPoliciesControllerTests [Theory] [BitAutoData(PermissionType.RunAsAdmin)] [BitAutoData(PermissionType.RunAsUserWithPermission)] - public async void GetPeoplePotentialGranteesAsync_ReturnsEmptyList( + public async void GetPeoplePotentialGrantees_ReturnsEmptyList( PermissionType permissionType, SutProvider sutProvider, Guid id) { - switch (permissionType) - { - case PermissionType.RunAsAdmin: - sutProvider.GetDependency().OrganizationAdmin(id).Returns(true); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - break; - case PermissionType.RunAsUserWithPermission: - sutProvider.GetDependency().OrganizationAdmin(id).Returns(false); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - break; - } - + SetupPermission(sutProvider, permissionType, id); var result = await sutProvider.Sut.GetPeoplePotentialGranteesAsync(id); await sutProvider.GetDependency().Received(1) @@ -239,7 +375,7 @@ public class AccessPoliciesControllerTests [Theory] [BitAutoData] - public async void GetPeoplePotentialGranteesAsync_UserWithoutPermission_Throws( + public async void GetPeoplePotentialGrantees_UserWithoutPermission_Throws( SutProvider sutProvider, Guid id) { @@ -262,26 +398,13 @@ public class AccessPoliciesControllerTests [Theory] [BitAutoData(PermissionType.RunAsAdmin)] [BitAutoData(PermissionType.RunAsUserWithPermission)] - public async void GetPeoplePotentialGranteesAsync_Success( + public async void GetPeoplePotentialGrantees_Success( PermissionType permissionType, SutProvider sutProvider, Guid id, Group mockGroup) { - switch (permissionType) - { - case PermissionType.RunAsAdmin: - sutProvider.GetDependency().OrganizationAdmin(id).Returns(true); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - break; - case PermissionType.RunAsUserWithPermission: - sutProvider.GetDependency().OrganizationAdmin(id).Returns(false); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - break; - } - + SetupPermission(sutProvider, permissionType, id); sutProvider.GetDependency().GetManyByOrganizationIdAsync(default) .ReturnsForAnyArgs(new List { mockGroup }); @@ -299,25 +422,12 @@ public class AccessPoliciesControllerTests [Theory] [BitAutoData(PermissionType.RunAsAdmin)] [BitAutoData(PermissionType.RunAsUserWithPermission)] - public async void GetServiceAccountsPotentialGranteesAsync_ReturnsEmptyList( + public async void GetServiceAccountsPotentialGrantees_ReturnsEmptyList( PermissionType permissionType, SutProvider sutProvider, Guid id) { - switch (permissionType) - { - case PermissionType.RunAsAdmin: - sutProvider.GetDependency().OrganizationAdmin(id).Returns(true); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - break; - case PermissionType.RunAsUserWithPermission: - sutProvider.GetDependency().OrganizationAdmin(id).Returns(false); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - break; - } - + SetupPermission(sutProvider, permissionType, id); var result = await sutProvider.Sut.GetServiceAccountsPotentialGranteesAsync(id); await sutProvider.GetDependency().Received(1) @@ -353,22 +463,9 @@ public class AccessPoliciesControllerTests Guid id, ServiceAccount mockServiceAccount) { - switch (permissionType) - { - case PermissionType.RunAsAdmin: - sutProvider.GetDependency().OrganizationAdmin(id).Returns(true); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - break; - case PermissionType.RunAsUserWithPermission: - sutProvider.GetDependency().OrganizationAdmin(id).Returns(false); - sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - break; - } - + SetupPermission(sutProvider, permissionType, id); sutProvider.GetDependency().GetManyByOrganizationIdWriteAccessAsync(default, default, default) - .ReturnsForAnyArgs(new List { mockServiceAccount }); + .ReturnsForAnyArgs(new List { mockServiceAccount }); var result = await sutProvider.Sut.GetServiceAccountsPotentialGranteesAsync(id); From d32cd79535561598c583ef8e3ca924ef217957af Mon Sep 17 00:00:00 2001 From: Stepan Goremykin Date: Wed, 8 Feb 2023 00:44:40 +0100 Subject: [PATCH 08/49] [PS-2425] Upgrade vulnerable packages (#2669) * Upgrade vulnerable packages * Added packages.lock.json files --------- Co-authored-by: Matt Gibson --- .../src/Commercial.Core/packages.lock.json | 26 +++++++++---------- .../packages.lock.json | 26 +++++++++---------- bitwarden_license/src/Scim/packages.lock.json | 26 +++++++++---------- bitwarden_license/src/Sso/packages.lock.json | 26 +++++++++---------- .../Commercial.Core.Test/packages.lock.json | 26 +++++++++---------- .../Scim.IntegrationTest/packages.lock.json | 26 +++++++++---------- .../test/Scim.Test/packages.lock.json | 26 +++++++++---------- perf/MicroBenchmarks/packages.lock.json | 26 +++++++++---------- src/Admin/packages.lock.json | 26 +++++++++---------- src/Api/packages.lock.json | 26 +++++++++---------- src/Billing/packages.lock.json | 26 +++++++++---------- src/Core/Core.csproj | 4 +-- src/Core/packages.lock.json | 26 +++++++++---------- src/Events/packages.lock.json | 26 +++++++++---------- src/EventsProcessor/packages.lock.json | 26 +++++++++---------- src/Icons/packages.lock.json | 26 +++++++++---------- src/Identity/packages.lock.json | 26 +++++++++---------- src/Infrastructure.Dapper/packages.lock.json | 26 +++++++++---------- .../packages.lock.json | 26 +++++++++---------- src/Notifications/packages.lock.json | 26 +++++++++---------- src/SharedWeb/packages.lock.json | 26 +++++++++---------- test/Api.IntegrationTest/packages.lock.json | 26 +++++++++---------- test/Api.Test/packages.lock.json | 26 +++++++++---------- test/Billing.Test/packages.lock.json | 26 +++++++++---------- test/Common/packages.lock.json | 26 +++++++++---------- test/Core.Test/packages.lock.json | 26 +++++++++---------- test/Icons.Test/packages.lock.json | 26 +++++++++---------- .../packages.lock.json | 26 +++++++++---------- test/Identity.Test/packages.lock.json | 26 +++++++++---------- .../packages.lock.json | 26 +++++++++---------- .../packages.lock.json | 26 +++++++++---------- test/IntegrationTestCommon/packages.lock.json | 26 +++++++++---------- util/Migrator/packages.lock.json | 26 +++++++++---------- util/MySqlMigrations/packages.lock.json | 26 +++++++++---------- util/PostgresMigrations/packages.lock.json | 26 +++++++++---------- util/Setup/packages.lock.json | 26 +++++++++---------- util/SqlServerEFScaffold/packages.lock.json | 26 +++++++++---------- util/SqliteMigrations/packages.lock.json | 26 +++++++++---------- 38 files changed, 483 insertions(+), 483 deletions(-) diff --git a/bitwarden_license/src/Commercial.Core/packages.lock.json b/bitwarden_license/src/Commercial.Core/packages.lock.json index 730790bc84..a13ec7e69a 100644 --- a/bitwarden_license/src/Commercial.Core/packages.lock.json +++ b/bitwarden_license/src/Commercial.Core/packages.lock.json @@ -45,8 +45,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -83,28 +83,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2549,8 +2549,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/packages.lock.json b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/packages.lock.json index f2b6cb0b24..3803bc704f 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/packages.lock.json +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/packages.lock.json @@ -63,8 +63,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -101,28 +101,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2722,8 +2722,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/bitwarden_license/src/Scim/packages.lock.json b/bitwarden_license/src/Scim/packages.lock.json index e0ee3394cd..3e79e8b6dd 100644 --- a/bitwarden_license/src/Scim/packages.lock.json +++ b/bitwarden_license/src/Scim/packages.lock.json @@ -71,8 +71,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -109,28 +109,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2998,8 +2998,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/bitwarden_license/src/Sso/packages.lock.json b/bitwarden_license/src/Sso/packages.lock.json index a55e1aa2be..7a7952f437 100644 --- a/bitwarden_license/src/Sso/packages.lock.json +++ b/bitwarden_license/src/Sso/packages.lock.json @@ -74,8 +74,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -112,28 +112,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2868,8 +2868,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/bitwarden_license/test/Commercial.Core.Test/packages.lock.json b/bitwarden_license/test/Commercial.Core.Test/packages.lock.json index b2acfe5143..9302ac09e7 100644 --- a/bitwarden_license/test/Commercial.Core.Test/packages.lock.json +++ b/bitwarden_license/test/Commercial.Core.Test/packages.lock.json @@ -105,8 +105,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -143,28 +143,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2818,8 +2818,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/bitwarden_license/test/Scim.IntegrationTest/packages.lock.json b/bitwarden_license/test/Scim.IntegrationTest/packages.lock.json index 787f71bdc9..74555c6867 100644 --- a/bitwarden_license/test/Scim.IntegrationTest/packages.lock.json +++ b/bitwarden_license/test/Scim.IntegrationTest/packages.lock.json @@ -143,8 +143,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -181,28 +181,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -3402,8 +3402,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/bitwarden_license/test/Scim.Test/packages.lock.json b/bitwarden_license/test/Scim.Test/packages.lock.json index 8f391f166c..e89d8735ff 100644 --- a/bitwarden_license/test/Scim.Test/packages.lock.json +++ b/bitwarden_license/test/Scim.Test/packages.lock.json @@ -131,8 +131,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -169,28 +169,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -3247,8 +3247,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/perf/MicroBenchmarks/packages.lock.json b/perf/MicroBenchmarks/packages.lock.json index 94f9b5bb6e..49a034c094 100644 --- a/perf/MicroBenchmarks/packages.lock.json +++ b/perf/MicroBenchmarks/packages.lock.json @@ -65,8 +65,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -103,28 +103,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2656,8 +2656,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/src/Admin/packages.lock.json b/src/Admin/packages.lock.json index 87e5807e1b..3c4729d2a4 100644 --- a/src/Admin/packages.lock.json +++ b/src/Admin/packages.lock.json @@ -94,8 +94,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -132,28 +132,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -3310,8 +3310,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/src/Api/packages.lock.json b/src/Api/packages.lock.json index 95bc2f7548..4df2bb85a3 100644 --- a/src/Api/packages.lock.json +++ b/src/Api/packages.lock.json @@ -85,8 +85,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -123,28 +123,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2794,8 +2794,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/src/Billing/packages.lock.json b/src/Billing/packages.lock.json index ae50591cf6..96fdbe7b72 100644 --- a/src/Billing/packages.lock.json +++ b/src/Billing/packages.lock.json @@ -74,8 +74,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -112,28 +112,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -3245,8 +3245,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index aabb3d71c4..0f4768125d 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -24,8 +24,8 @@ - - + + diff --git a/src/Core/packages.lock.json b/src/Core/packages.lock.json index 3d8b2c9d00..8af14081d8 100644 --- a/src/Core/packages.lock.json +++ b/src/Core/packages.lock.json @@ -55,21 +55,21 @@ }, "Azure.Storage.Blobs": { "type": "Direct", - "requested": "[12.11.0, )", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "requested": "[12.14.1, )", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Queues": { "type": "Direct", - "requested": "[12.9.0, )", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "requested": "[12.12.0, )", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -390,8 +390,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -418,10 +418,10 @@ }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, diff --git a/src/Events/packages.lock.json b/src/Events/packages.lock.json index 5ae9cb53b9..51058788bf 100644 --- a/src/Events/packages.lock.json +++ b/src/Events/packages.lock.json @@ -62,8 +62,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -100,28 +100,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2726,8 +2726,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/src/EventsProcessor/packages.lock.json b/src/EventsProcessor/packages.lock.json index 5ae9cb53b9..51058788bf 100644 --- a/src/EventsProcessor/packages.lock.json +++ b/src/EventsProcessor/packages.lock.json @@ -62,8 +62,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -100,28 +100,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2726,8 +2726,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/src/Icons/packages.lock.json b/src/Icons/packages.lock.json index e74e2984da..7ff4b356d9 100644 --- a/src/Icons/packages.lock.json +++ b/src/Icons/packages.lock.json @@ -72,8 +72,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -110,28 +110,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2736,8 +2736,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/src/Identity/packages.lock.json b/src/Identity/packages.lock.json index 1921dc5475..6ae150fce6 100644 --- a/src/Identity/packages.lock.json +++ b/src/Identity/packages.lock.json @@ -71,8 +71,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -109,28 +109,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2748,8 +2748,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/src/Infrastructure.Dapper/packages.lock.json b/src/Infrastructure.Dapper/packages.lock.json index 4909ebfa4b..e0adb36cba 100644 --- a/src/Infrastructure.Dapper/packages.lock.json +++ b/src/Infrastructure.Dapper/packages.lock.json @@ -51,8 +51,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -89,28 +89,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2555,8 +2555,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/src/Infrastructure.EntityFramework/packages.lock.json b/src/Infrastructure.EntityFramework/packages.lock.json index 38a0d36178..436512dacd 100644 --- a/src/Infrastructure.EntityFramework/packages.lock.json +++ b/src/Infrastructure.EntityFramework/packages.lock.json @@ -126,8 +126,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -164,28 +164,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2728,8 +2728,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/src/Notifications/packages.lock.json b/src/Notifications/packages.lock.json index f50b74f911..9def1f8d36 100644 --- a/src/Notifications/packages.lock.json +++ b/src/Notifications/packages.lock.json @@ -83,8 +83,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -121,28 +121,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2776,8 +2776,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/src/SharedWeb/packages.lock.json b/src/SharedWeb/packages.lock.json index 53edf4ea45..6ba839fea5 100644 --- a/src/SharedWeb/packages.lock.json +++ b/src/SharedWeb/packages.lock.json @@ -62,8 +62,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -100,28 +100,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2726,8 +2726,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/test/Api.IntegrationTest/packages.lock.json b/test/Api.IntegrationTest/packages.lock.json index 2e0ce267e0..a711f75622 100644 --- a/test/Api.IntegrationTest/packages.lock.json +++ b/test/Api.IntegrationTest/packages.lock.json @@ -122,8 +122,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -170,28 +170,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -3189,8 +3189,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/test/Api.Test/packages.lock.json b/test/Api.Test/packages.lock.json index 2f5a90cb9b..de91a99242 100644 --- a/test/Api.Test/packages.lock.json +++ b/test/Api.Test/packages.lock.json @@ -132,8 +132,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -180,28 +180,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -3068,8 +3068,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/test/Billing.Test/packages.lock.json b/test/Billing.Test/packages.lock.json index d6d99f2705..52ad5d31db 100644 --- a/test/Billing.Test/packages.lock.json +++ b/test/Billing.Test/packages.lock.json @@ -132,8 +132,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -170,28 +170,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -3498,8 +3498,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/test/Common/packages.lock.json b/test/Common/packages.lock.json index a404c169d9..93c85ee02a 100644 --- a/test/Common/packages.lock.json +++ b/test/Common/packages.lock.json @@ -120,8 +120,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -158,28 +158,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2789,8 +2789,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/test/Core.Test/packages.lock.json b/test/Core.Test/packages.lock.json index a978e6e840..0f54629a53 100644 --- a/test/Core.Test/packages.lock.json +++ b/test/Core.Test/packages.lock.json @@ -136,8 +136,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -174,28 +174,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2817,8 +2817,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/test/Icons.Test/packages.lock.json b/test/Icons.Test/packages.lock.json index 9c973218a5..3222145ccc 100644 --- a/test/Icons.Test/packages.lock.json +++ b/test/Icons.Test/packages.lock.json @@ -113,8 +113,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -151,28 +151,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2911,8 +2911,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/test/Identity.IntegrationTest/packages.lock.json b/test/Identity.IntegrationTest/packages.lock.json index 35c0b92c62..520d2901c2 100644 --- a/test/Identity.IntegrationTest/packages.lock.json +++ b/test/Identity.IntegrationTest/packages.lock.json @@ -143,8 +143,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -181,28 +181,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -3136,8 +3136,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/test/Identity.Test/packages.lock.json b/test/Identity.Test/packages.lock.json index f1ceb6d592..223f295510 100644 --- a/test/Identity.Test/packages.lock.json +++ b/test/Identity.Test/packages.lock.json @@ -132,8 +132,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -170,28 +170,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -3003,8 +3003,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/test/Infrastructure.EFIntegration.Test/packages.lock.json b/test/Infrastructure.EFIntegration.Test/packages.lock.json index 385dc20ec8..0194056cde 100644 --- a/test/Infrastructure.EFIntegration.Test/packages.lock.json +++ b/test/Infrastructure.EFIntegration.Test/packages.lock.json @@ -143,8 +143,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -181,28 +181,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2993,8 +2993,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/test/Infrastructure.IntegrationTest/packages.lock.json b/test/Infrastructure.IntegrationTest/packages.lock.json index 0049afce28..876a4117e4 100644 --- a/test/Infrastructure.IntegrationTest/packages.lock.json +++ b/test/Infrastructure.IntegrationTest/packages.lock.json @@ -134,8 +134,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -172,28 +172,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2841,8 +2841,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/test/IntegrationTestCommon/packages.lock.json b/test/IntegrationTestCommon/packages.lock.json index fc0eb7b3a1..e65cbeb55d 100644 --- a/test/IntegrationTestCommon/packages.lock.json +++ b/test/IntegrationTestCommon/packages.lock.json @@ -119,8 +119,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -157,28 +157,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -3122,8 +3122,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/util/Migrator/packages.lock.json b/util/Migrator/packages.lock.json index 34bf0f9028..02ea2191ba 100644 --- a/util/Migrator/packages.lock.json +++ b/util/Migrator/packages.lock.json @@ -69,8 +69,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -107,28 +107,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2609,8 +2609,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/util/MySqlMigrations/packages.lock.json b/util/MySqlMigrations/packages.lock.json index 257b0d059c..996841866c 100644 --- a/util/MySqlMigrations/packages.lock.json +++ b/util/MySqlMigrations/packages.lock.json @@ -72,8 +72,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -110,28 +110,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2736,8 +2736,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/util/PostgresMigrations/packages.lock.json b/util/PostgresMigrations/packages.lock.json index 257b0d059c..996841866c 100644 --- a/util/PostgresMigrations/packages.lock.json +++ b/util/PostgresMigrations/packages.lock.json @@ -72,8 +72,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -110,28 +110,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2736,8 +2736,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/util/Setup/packages.lock.json b/util/Setup/packages.lock.json index 916391b7b7..a9543d7815 100644 --- a/util/Setup/packages.lock.json +++ b/util/Setup/packages.lock.json @@ -73,8 +73,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -111,28 +111,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2615,8 +2615,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/util/SqlServerEFScaffold/packages.lock.json b/util/SqlServerEFScaffold/packages.lock.json index 34679ed985..63f387a6b1 100644 --- a/util/SqlServerEFScaffold/packages.lock.json +++ b/util/SqlServerEFScaffold/packages.lock.json @@ -72,8 +72,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -120,28 +120,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2818,8 +2818,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", diff --git a/util/SqliteMigrations/packages.lock.json b/util/SqliteMigrations/packages.lock.json index 257b0d059c..996841866c 100644 --- a/util/SqliteMigrations/packages.lock.json +++ b/util/SqliteMigrations/packages.lock.json @@ -72,8 +72,8 @@ }, "Azure.Core": { "type": "Transitive", - "resolved": "1.24.0", - "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "resolved": "1.25.0", + "contentHash": "X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "1.1.1", "System.Diagnostics.DiagnosticSource": "4.6.0", @@ -110,28 +110,28 @@ }, "Azure.Storage.Blobs": { "type": "Transitive", - "resolved": "12.11.0", - "contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==", + "resolved": "12.14.1", + "contentHash": "DvRBWUDMB2LjdRbsBNtz/LiVIYk56hqzSooxx4uq4rCdLj2M+7Vvoa1r+W35Dz6ZXL6p+SNcgEae3oZ+CkPfow==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Text.Json": "4.7.2" } }, "Azure.Storage.Common": { "type": "Transitive", - "resolved": "12.10.0", - "contentHash": "vYkHGzUkdZTace/cDPZLG+Mh/EoPqQuGxDIBOau9D+XWoDPmuUFGk325aXplkFE4JFGpSwoytNYzk/qBCaiHqg==", + "resolved": "12.13.0", + "contentHash": "jDv8xJWeZY2Er9zA6QO25BiGolxg87rItt9CwAp7L/V9EPJeaz8oJydaNL9Wj0+3ncceoMgdiyEv66OF8YUwWQ==", "dependencies": { - "Azure.Core": "1.22.0", + "Azure.Core": "1.25.0", "System.IO.Hashing": "6.0.0" } }, "Azure.Storage.Queues": { "type": "Transitive", - "resolved": "12.9.0", - "contentHash": "jDiyHtsCUCrWNvZW7SjJnJb46UhpdgQrWCbL8aWpapDHlq9LvbvxYpfLh4dfKAz09QiTznLMIU3i+md9+7GzqQ==", + "resolved": "12.12.0", + "contentHash": "PwrfymLYFmmOt6A0vMiDVhBV7RoOAKftzzvrbSM3W9cJKpkxAg57AhM7/wbNb3P8Uq0B73lBurkFiFzWK9PXHg==", "dependencies": { - "Azure.Storage.Common": "12.10.0", + "Azure.Storage.Common": "12.13.0", "System.Memory.Data": "1.0.2", "System.Text.Json": "4.7.2" } @@ -2736,8 +2736,8 @@ "AspNetCoreRateLimit": "[4.0.2, )", "AspNetCoreRateLimit.Redis": "[1.0.1, )", "Azure.Extensions.AspNetCore.DataProtection.Blobs": "[1.2.1, )", - "Azure.Storage.Blobs": "[12.11.0, )", - "Azure.Storage.Queues": "[12.9.0, )", + "Azure.Storage.Blobs": "[12.14.1, )", + "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", "Fido2.AspNet": "[3.0.1, )", From e3fdb2636bd56e5e5d44543fbef596532d61a58f Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 8 Feb 2023 10:39:40 +0100 Subject: [PATCH 09/49] [EC-1055] [Tech Debt/Bug] Sql.sqlproj is missing sprocs (#2664) * [EC-1055] fix: add missing sprocs to sqlproj * [EC-1055] fix: remove duplicate sproc --- src/Sql/Sql.sqlproj | 7 +++++++ src/Sql/dbo/Stored Procedures/Event_ReadyById.sql | 13 ------------- 2 files changed, 7 insertions(+), 13 deletions(-) delete mode 100644 src/Sql/dbo/Stored Procedures/Event_ReadyById.sql diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 1ffe20cc11..9d7db8137d 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -128,22 +128,29 @@ + + + + + + + diff --git a/src/Sql/dbo/Stored Procedures/Event_ReadyById.sql b/src/Sql/dbo/Stored Procedures/Event_ReadyById.sql deleted file mode 100644 index 71e3b28660..0000000000 --- a/src/Sql/dbo/Stored Procedures/Event_ReadyById.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE PROCEDURE [dbo].[Event_ReadById] - @Id UNIQUEIDENTIFIER -AS -BEGIN - SET NOCOUNT ON - - SELECT - * - FROM - [dbo].[Event] - WHERE - [Id] = @Id -END From 04c4be8a15d82af6cc091398faecb89475d9e0bf Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 8 Feb 2023 16:33:45 +0100 Subject: [PATCH 10/49] [EC-489] chore: remove obsolete `identifier` field (#2635) * [EC-489] chore: remove obsolete `identifier` field * [EC-489] chore: remove identifier from org response model --- .../Request/Organizations/OrganizationUpdateRequestModel.cs | 4 ---- .../Response/Organizations/OrganizationResponseModel.cs | 2 -- 2 files changed, 6 deletions(-) diff --git a/src/Api/Models/Request/Organizations/OrganizationUpdateRequestModel.cs b/src/Api/Models/Request/Organizations/OrganizationUpdateRequestModel.cs index 0bd16767d2..79983a5549 100644 --- a/src/Api/Models/Request/Organizations/OrganizationUpdateRequestModel.cs +++ b/src/Api/Models/Request/Organizations/OrganizationUpdateRequestModel.cs @@ -12,9 +12,6 @@ public class OrganizationUpdateRequestModel public string Name { get; set; } [StringLength(50)] public string BusinessName { get; set; } - [Obsolete("2022-08-03 Moved to Org SSO request model, left for backwards compatability. Remove with EC-489.")] - [StringLength(50)] - public string Identifier { get; set; } [EmailAddress] [Required] [StringLength(256)] @@ -31,7 +28,6 @@ public class OrganizationUpdateRequestModel existingOrganization.BusinessName = BusinessName; existingOrganization.BillingEmail = BillingEmail?.ToLowerInvariant()?.Trim(); } - existingOrganization.Identifier = Identifier; Keys?.ToOrganization(existingOrganization); return existingOrganization; } diff --git a/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs b/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs index 4df4a4dce2..0e284ab9cb 100644 --- a/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs +++ b/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs @@ -17,7 +17,6 @@ public class OrganizationResponseModel : ResponseModel } Id = organization.Id.ToString(); - Identifier = organization.Identifier; Name = organization.Name; BusinessName = organization.BusinessName; BusinessAddress1 = organization.BusinessAddress1; @@ -51,7 +50,6 @@ public class OrganizationResponseModel : ResponseModel } public string Id { get; set; } - public string Identifier { get; set; } public string Name { get; set; } public string BusinessName { get; set; } public string BusinessAddress1 { get; set; } From 35b832dbc9abfb20f620a31a9a9b887b6e3289db Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Thu, 9 Feb 2023 08:58:05 -0600 Subject: [PATCH 11/49] [SM-485] Add access policy on project creation (#2678) * Add bootstrap access policy on create * Update project integration tests --- .../Commands/Projects/CreateProjectCommand.cs | 29 +- .../Projects/CreateProjectCommandTests.cs | 40 +++ .../Controllers/ProjectsController.cs | 3 +- .../Interfaces/ICreateProjectCommand.cs | 2 +- .../Controllers/ProjectsControllerTest.cs | 279 ++++++++++++------ .../AccessPoliciesControllerTests.cs | 7 +- .../Controllers/ProjectsControllerTests.cs | 221 +++++++++++++- .../SecretsManager/Enums/PermissionType.cs | 7 + 8 files changed, 476 insertions(+), 112 deletions(-) create mode 100644 bitwarden_license/test/Commercial.Core.Test/SecretsManager/Projects/CreateProjectCommandTests.cs create mode 100644 test/Api.Test/SecretsManager/Enums/PermissionType.cs diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Projects/CreateProjectCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Projects/CreateProjectCommand.cs index 6e86088dcb..055fda0c8b 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Projects/CreateProjectCommand.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Projects/CreateProjectCommand.cs @@ -1,4 +1,5 @@ -using Bit.Core.SecretsManager.Commands.Projects.Interfaces; +using Bit.Core.Repositories; +using Bit.Core.SecretsManager.Commands.Projects.Interfaces; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; @@ -6,15 +7,35 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.Projects; public class CreateProjectCommand : ICreateProjectCommand { + private readonly IAccessPolicyRepository _accessPolicyRepository; + private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IProjectRepository _projectRepository; - public CreateProjectCommand(IProjectRepository projectRepository) + public CreateProjectCommand( + IAccessPolicyRepository accessPolicyRepository, + IOrganizationUserRepository organizationUserRepository, + IProjectRepository projectRepository + ) { + _accessPolicyRepository = accessPolicyRepository; + _organizationUserRepository = organizationUserRepository; _projectRepository = projectRepository; } - public async Task CreateAsync(Project project) + public async Task CreateAsync(Project project, Guid userId) { - return await _projectRepository.CreateAsync(project); + var createdProject = await _projectRepository.CreateAsync(project); + + var orgUser = await _organizationUserRepository.GetByOrganizationAsync(createdProject.OrganizationId, + userId); + var accessPolicy = new UserProjectAccessPolicy() + { + OrganizationUserId = orgUser.Id, + GrantedProjectId = createdProject.Id, + Read = true, + Write = true, + }; + await _accessPolicyRepository.CreateManyAsync(new List { accessPolicy }); + return createdProject; } } diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Projects/CreateProjectCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Projects/CreateProjectCommandTests.cs new file mode 100644 index 0000000000..3739d4699f --- /dev/null +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Projects/CreateProjectCommandTests.cs @@ -0,0 +1,40 @@ +using Bit.Commercial.Core.SecretsManager.Commands.Projects; +using Bit.Core.Entities; +using Bit.Core.Repositories; +using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Commercial.Core.Test.SecretsManager.Projects; + +[SutProviderCustomize] +[ProjectCustomize] +public class CreateProjectCommandTests +{ + [Theory] + [BitAutoData] + public async Task CreateAsync_CallsCreate(Project data, + Guid userId, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .GetByOrganizationAsync(Arg.Any(), Arg.Any()) + .Returns(new OrganizationUser() { Id = userId }); + + sutProvider.GetDependency() + .CreateAsync(Arg.Any()) + .Returns(data); + + await sutProvider.Sut.CreateAsync(data, userId); + + await sutProvider.GetDependency().Received(1) + .CreateAsync(Arg.Is(data)); + + await sutProvider.GetDependency().Received(1) + .CreateManyAsync(Arg.Any>()); + } +} diff --git a/src/Api/SecretsManager/Controllers/ProjectsController.cs b/src/Api/SecretsManager/Controllers/ProjectsController.cs index 437079d87d..9dfb8f11cc 100644 --- a/src/Api/SecretsManager/Controllers/ProjectsController.cs +++ b/src/Api/SecretsManager/Controllers/ProjectsController.cs @@ -65,7 +65,8 @@ public class ProjectsController : Controller throw new NotFoundException(); } - var result = await _createProjectCommand.CreateAsync(createRequest.ToProject(organizationId)); + var userId = _userService.GetProperUserId(User).Value; + var result = await _createProjectCommand.CreateAsync(createRequest.ToProject(organizationId), userId); return new ProjectResponseModel(result); } diff --git a/src/Core/SecretsManager/Commands/Projects/Interfaces/ICreateProjectCommand.cs b/src/Core/SecretsManager/Commands/Projects/Interfaces/ICreateProjectCommand.cs index c5f43d360e..50cd714cb8 100644 --- a/src/Core/SecretsManager/Commands/Projects/Interfaces/ICreateProjectCommand.cs +++ b/src/Core/SecretsManager/Commands/Projects/Interfaces/ICreateProjectCommand.cs @@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.Projects.Interfaces; public interface ICreateProjectCommand { - Task CreateAsync(Project project); + Task CreateAsync(Project project, Guid userId); } diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTest.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTest.cs index 1b6db5c9c9..fa9dccc939 100644 --- a/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTest.cs +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/ProjectsControllerTest.cs @@ -1,9 +1,11 @@ using System.Net; using System.Net.Http.Headers; using Bit.Api.IntegrationTest.Factories; +using Bit.Api.IntegrationTest.SecretsManager.Enums; using Bit.Api.Models.Response; using Bit.Api.SecretsManager.Models.Request; using Bit.Api.SecretsManager.Models.Response; +using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; @@ -20,6 +22,7 @@ public class ProjectsControllerTest : IClassFixture, IAsy private readonly HttpClient _client; private readonly ApiApplicationFactory _factory; private readonly IProjectRepository _projectRepository; + private readonly IAccessPolicyRepository _accessPolicyRepository; private string _email = null!; private SecretsManagerOrganizationHelper _organizationHelper = null!; @@ -29,6 +32,7 @@ public class ProjectsControllerTest : IClassFixture, IAsy _factory = factory; _client = _factory.CreateClient(); _projectRepository = _factory.GetService(); + _accessPolicyRepository = _factory.GetService(); } public async Task InitializeAsync() @@ -64,21 +68,28 @@ public class ProjectsControllerTest : IClassFixture, IAsy } [Fact] - public async Task ListByOrganization_Success() + public async Task ListByOrganization_UserWithoutPermission_EmptyList() { var (org, _) = await _organizationHelper.Initialize(true, true); - await LoginAsync(_email); + var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); - var projectIds = new List(); - for (var i = 0; i < 3; i++) - { - var project = await _projectRepository.CreateAsync(new Project - { - OrganizationId = org.Id, - Name = _mockEncryptedString - }); - projectIds.Add(project.Id); - } + await CreateProjectsAsync(org.Id); + + var response = await _client.GetAsync($"/organizations/{org.Id}/projects"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync>(); + Assert.NotNull(result); + Assert.Empty(result!.Data); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task ListByOrganization_Success(PermissionType permissionType) + { + var (projectIds, org) = await SetupProjectsWithAccessAsync(permissionType); var response = await _client.GetAsync($"/organizations/{org.Id}/projects"); response.EnsureSuccessStatusCode(); @@ -104,11 +115,22 @@ public class ProjectsControllerTest : IClassFixture, IAsy Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } - [Fact] - public async Task Create_Success() + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task Create_Success(PermissionType permissionType) { - var (org, _) = await _organizationHelper.Initialize(true, true); + var (org, adminOrgUser) = await _organizationHelper.Initialize(true, true); await LoginAsync(_email); + var orgUserId = adminOrgUser.Id; + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + orgUserId = orgUser.Id; + } + var request = new ProjectCreateRequestModel { Name = _mockEncryptedString }; var response = await _client.PostAsJsonAsync($"/organizations/{org.Id}/projects", request); @@ -126,6 +148,17 @@ public class ProjectsControllerTest : IClassFixture, IAsy AssertHelper.AssertRecent(createdProject.RevisionDate); AssertHelper.AssertRecent(createdProject.CreationDate); Assert.Null(createdProject.DeletedDate); + + // Check permissions have been bootstrapped. + var accessPolicies = await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(createdProject.Id); + Assert.NotNull(accessPolicies); + var ap = (UserProjectAccessPolicy)accessPolicies.First(); + Assert.Equal(createdProject.Id, ap.GrantedProjectId); + Assert.Equal(orgUserId, ap.OrganizationUserId); + Assert.True(ap.Read); + Assert.True(ap.Write); + AssertHelper.AssertRecent(ap.CreationDate); + AssertHelper.AssertRecent(ap.RevisionDate); } [Theory] @@ -140,34 +173,28 @@ public class ProjectsControllerTest : IClassFixture, IAsy var initialProject = await _projectRepository.CreateAsync(new Project { OrganizationId = org.Id, - Name = _mockEncryptedString + Name = _mockEncryptedString, }); - var mockEncryptedString2 = "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98xy4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg="; + var mockEncryptedString2 = + "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98xy4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg="; var request = new ProjectCreateRequestModel { Name = mockEncryptedString2 }; var response = await _client.PutAsJsonAsync($"/projects/{initialProject.Id}", request); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } - [Fact] - public async Task Update_Success() + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task Update_Success(PermissionType permissionType) { - var (org, _) = await _organizationHelper.Initialize(true, true); - await LoginAsync(_email); + var initialProject = await SetupProjectWithAccessAsync(permissionType); - var initialProject = await _projectRepository.CreateAsync(new Project - { - OrganizationId = org.Id, - Name = _mockEncryptedString - }); + var mockEncryptedString2 = + "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98xy4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg="; - var mockEncryptedString2 = "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98xy4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg="; - - var request = new ProjectUpdateRequestModel - { - Name = mockEncryptedString2 - }; + var request = new ProjectUpdateRequestModel { Name = mockEncryptedString2 }; var response = await _client.PutAsJsonAsync($"/projects/{initialProject.Id}", request); response.EnsureSuccessStatusCode(); @@ -180,21 +207,21 @@ public class ProjectsControllerTest : IClassFixture, IAsy Assert.NotNull(result); Assert.Equal(request.Name, updatedProject.Name); AssertHelper.AssertRecent(updatedProject.RevisionDate); - AssertHelper.AssertRecent(updatedProject.CreationDate); Assert.Null(updatedProject.DeletedDate); Assert.NotEqual(initialProject.Name, updatedProject.Name); Assert.NotEqual(initialProject.RevisionDate, updatedProject.RevisionDate); } [Fact] - public async Task Update_NonExistingProject_Throws_NotFound() + public async Task Update_NonExistingProject_NotFound() { await _organizationHelper.Initialize(true, true); await LoginAsync(_email); var request = new ProjectUpdateRequestModel { - Name = "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98xy4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg=", + Name = + "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98xy4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg=", }; var response = await _client.PutAsJsonAsync("/projects/c53de509-4581-402c-8cbd-f26d2c516fba", request); @@ -203,7 +230,7 @@ public class ProjectsControllerTest : IClassFixture, IAsy } [Fact] - public async Task Update_MissingAccessPolicy_Throws_NotFound() + public async Task Update_MissingAccessPolicy_NotFound() { var (org, _) = await _organizationHelper.Initialize(true, true); var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); @@ -212,12 +239,13 @@ public class ProjectsControllerTest : IClassFixture, IAsy var project = await _projectRepository.CreateAsync(new Project { OrganizationId = org.Id, - Name = _mockEncryptedString + Name = _mockEncryptedString, }); var request = new ProjectUpdateRequestModel { - Name = "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98xy4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg=", + Name = + "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98xy4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg=", }; var response = await _client.PutAsJsonAsync($"/projects/{project.Id}", request); @@ -237,10 +265,11 @@ public class ProjectsControllerTest : IClassFixture, IAsy var project = await _projectRepository.CreateAsync(new Project { OrganizationId = org.Id, - Name = _mockEncryptedString + Name = _mockEncryptedString, }); - var mockEncryptedString2 = "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98xy4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg="; + var mockEncryptedString2 = + "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98xy4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg="; var request = new ProjectCreateRequestModel { Name = mockEncryptedString2 }; var response = await _client.PutAsJsonAsync($"/projects/{project.Id}", request); @@ -248,27 +277,7 @@ public class ProjectsControllerTest : IClassFixture, IAsy } [Fact] - public async Task Get_Success() - { - var (org, _) = await _organizationHelper.Initialize(true, true); - await LoginAsync(_email); - - var createdProject = await _projectRepository.CreateAsync(new Project - { - OrganizationId = org.Id, - Name = _mockEncryptedString - }); - - var response = await _client.GetAsync($"/projects/{createdProject.Id}"); - response.EnsureSuccessStatusCode(); - var result = await response.Content.ReadFromJsonAsync(); - Assert.Equal(createdProject.Name, result!.Name); - Assert.Equal(createdProject.RevisionDate, result.RevisionDate); - Assert.Equal(createdProject.CreationDate, result.CreationDate); - } - - [Fact] - public async Task Get_MissingAccessPolicy_Throws_NotFound() + public async Task Get_MissingAccessPolicy_NotFound() { var (org, _) = await _organizationHelper.Initialize(true, true); var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); @@ -277,13 +286,28 @@ public class ProjectsControllerTest : IClassFixture, IAsy var createdProject = await _projectRepository.CreateAsync(new Project { OrganizationId = org.Id, - Name = _mockEncryptedString + Name = _mockEncryptedString, }); var response = await _client.GetAsync($"/projects/{createdProject.Id}"); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task Get_Success(PermissionType permissionType) + { + var project = await SetupProjectWithAccessAsync(permissionType); + + var response = await _client.GetAsync($"/projects/{project.Id}"); + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadFromJsonAsync(); + Assert.Equal(project.Name, result!.Name); + Assert.Equal(project.RevisionDate, result.RevisionDate); + Assert.Equal(project.CreationDate, result.CreationDate); + } + [Theory] [InlineData(false, false)] [InlineData(true, false)] @@ -293,53 +317,124 @@ public class ProjectsControllerTest : IClassFixture, IAsy var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); await LoginAsync(_email); - var projectIds = new List(); - for (var i = 0; i < 3; i++) - { - var project = await _projectRepository.CreateAsync(new Project - { - OrganizationId = org.Id, - Name = _mockEncryptedString, - }); - projectIds.Add(project.Id); - } + var projectIds = await CreateProjectsAsync(org.Id); var response = await _client.PostAsync("/projects/delete", JsonContent.Create(projectIds)); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } [Fact] - public async Task Delete_Success() + public async Task Delete_MissingAccessPolicy_AccessDenied() { var (org, _) = await _organizationHelper.Initialize(true, true); - await LoginAsync(_email); + var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); - var projectIds = new List(); - for (var i = 0; i < 3; i++) - { - var project = await _projectRepository.CreateAsync(new Project - { - OrganizationId = org.Id, - Name = _mockEncryptedString, - }); - projectIds.Add(project.Id); - } + var projectIds = await CreateProjectsAsync(org.Id); + + var response = await _client.PostAsync("/projects/delete", JsonContent.Create(projectIds)); + + var results = await response.Content.ReadFromJsonAsync>(); + Assert.NotNull(results); + Assert.Equal(projectIds.OrderBy(x => x), + results!.Data.Select(x => x.Id).OrderBy(x => x)); + Assert.All(results.Data, item => Assert.Equal("access denied", item.Error)); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task Delete_Success(PermissionType permissionType) + { + var (projectIds, _) = await SetupProjectsWithAccessAsync(permissionType); var response = await _client.PostAsync("/projects/delete", JsonContent.Create(projectIds)); response.EnsureSuccessStatusCode(); var results = await response.Content.ReadFromJsonAsync>(); Assert.NotNull(results); - - var index = 0; - foreach (var result in results!.Data) - { - Assert.Equal(projectIds[index], result.Id); - Assert.Null(result.Error); - index++; - } + Assert.Equal(projectIds.OrderBy(x => x), + results!.Data.Select(x => x.Id).OrderBy(x => x)); + Assert.DoesNotContain(results.Data, x => x.Error != null); var projects = await _projectRepository.GetManyByIds(projectIds); Assert.Empty(projects); } + + private async Task> CreateProjectsAsync(Guid orgId, int numberToCreate = 3) + { + var projectIds = new List(); + for (var i = 0; i < numberToCreate; i++) + { + var project = await _projectRepository.CreateAsync(new Project + { + OrganizationId = orgId, + Name = _mockEncryptedString, + }); + projectIds.Add(project.Id); + } + + return projectIds; + } + + private async Task<(List, Organization)> SetupProjectsWithAccessAsync(PermissionType permissionType, + int projectsToCreate = 3) + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + var projectIds = await CreateProjectsAsync(org.Id, projectsToCreate); + + if (permissionType == PermissionType.RunAsAdmin) + { + return (projectIds, org); + } + + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var accessPolicies = projectIds.Select(projectId => new UserProjectAccessPolicy + { + GrantedProjectId = projectId, + OrganizationUserId = orgUser.Id, + Read = true, + Write = true, + }) + .Cast() + .ToList(); + + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + + return (projectIds, org); + } + + private async Task SetupProjectWithAccessAsync(PermissionType permissionType) + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + + var initialProject = await _projectRepository.CreateAsync(new Project + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + if (permissionType == PermissionType.RunAsAdmin) + { + return initialProject; + } + + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var accessPolicies = new List + { + new UserProjectAccessPolicy + { + GrantedProjectId = initialProject.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + + return initialProject; + } } diff --git a/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs index 14b4774677..2cd3bf7748 100644 --- a/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs +++ b/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs @@ -1,5 +1,6 @@ using Bit.Api.SecretsManager.Controllers; using Bit.Api.SecretsManager.Models.Request; +using Bit.Api.Test.SecretsManager.Enums; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; @@ -25,12 +26,6 @@ namespace Bit.Api.Test.SecretsManager.Controllers; [JsonDocumentCustomize] public class AccessPoliciesControllerTests { - public enum PermissionType - { - RunAsAdmin, - RunAsUserWithPermission, - } - private static void SetupAdmin(SutProvider sutProvider, Guid organizationId) { sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); diff --git a/test/Api.Test/SecretsManager/Controllers/ProjectsControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/ProjectsControllerTests.cs index 747d213269..39ba7bdba7 100644 --- a/test/Api.Test/SecretsManager/Controllers/ProjectsControllerTests.cs +++ b/test/Api.Test/SecretsManager/Controllers/ProjectsControllerTests.cs @@ -1,10 +1,17 @@ using Bit.Api.SecretsManager.Controllers; +using Bit.Api.SecretsManager.Models.Request; +using Bit.Api.Test.SecretsManager.Enums; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Commands.Projects.Interfaces; using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Repositories; using Bit.Core.Services; using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; +using Bit.Test.Common.Helpers; using NSubstitute; using Xunit; @@ -16,28 +23,226 @@ namespace Bit.Api.Test.SecretsManager.Controllers; [JsonDocumentCustomize] public class ProjectsControllerTests { + private static void SetupAdmin(SutProvider sutProvider, Guid organizationId) + { + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(true); + } + + private static void SetupUserWithPermission(SutProvider sutProvider, Guid organizationId) + { + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(false); + sutProvider.GetDependency().OrganizationUser(default).ReturnsForAnyArgs(true); + } + + [Theory] + [BitAutoData] + public async void ListByOrganization_SmNotEnabled_Throws(SutProvider sutProvider, Guid data) + { + sutProvider.GetDependency().AccessSecretsManager(data).Returns(false); + + await Assert.ThrowsAsync(() => sutProvider.Sut.ListByOrganizationAsync(data)); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void ListByOrganization_ReturnsEmptyList(PermissionType permissionType, + SutProvider sutProvider, Guid data) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, data); + break; + case PermissionType.RunAsUserWithPermission: + SetupUserWithPermission(sutProvider, data); + break; + } + + var result = await sutProvider.Sut.ListByOrganizationAsync(data); + + await sutProvider.GetDependency().Received(1) + .GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data)), Arg.Any(), + Arg.Any()); + Assert.Empty(result.Data); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void ListByOrganization_Success(PermissionType permissionType, + SutProvider sutProvider, Guid data, Project mockProject) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, data); + break; + case PermissionType.RunAsUserWithPermission: + SetupUserWithPermission(sutProvider, data); + break; + } + + sutProvider.GetDependency().GetManyByOrganizationIdAsync(default, default, default) + .ReturnsForAnyArgs(new List { mockProject }); + + var result = await sutProvider.Sut.ListByOrganizationAsync(data); + + await sutProvider.GetDependency().Received(1) + .GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data)), Arg.Any(), + Arg.Any()); + Assert.NotEmpty(result.Data); + Assert.Single(result.Data); + } + + [Theory] + [BitAutoData] + public async void Create_SmNotEnabled_Throws(SutProvider sutProvider, Guid orgId, + ProjectCreateRequestModel data) + { + sutProvider.GetDependency().AccessSecretsManager(orgId).Returns(false); + + await Assert.ThrowsAsync(() => sutProvider.Sut.CreateAsync(orgId, data)); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateAsync(Arg.Any(), Arg.Any()); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void Create_Success(PermissionType permissionType, SutProvider sutProvider, + Guid orgId, ProjectCreateRequestModel data) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, orgId); + break; + case PermissionType.RunAsUserWithPermission: + SetupUserWithPermission(sutProvider, orgId); + break; + } + + var resultProject = data.ToProject(orgId); + sutProvider.GetDependency().CreateAsync(default, default) + .ReturnsForAnyArgs(resultProject); + + await sutProvider.Sut.CreateAsync(orgId, data); + + await sutProvider.GetDependency().Received(1) + .CreateAsync(Arg.Any(), Arg.Any()); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void Update_Success(PermissionType permissionType, SutProvider sutProvider, + Guid orgId, ProjectUpdateRequestModel data) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, orgId); + break; + case PermissionType.RunAsUserWithPermission: + SetupUserWithPermission(sutProvider, orgId); + break; + } + + var resultProject = data.ToProject(orgId); + sutProvider.GetDependency().UpdateAsync(default, default) + .ReturnsForAnyArgs(resultProject); + + await sutProvider.Sut.UpdateAsync(orgId, data); + + await sutProvider.GetDependency().Received(1) + .UpdateAsync(Arg.Any(), Arg.Any()); + } + + [Theory] + [BitAutoData] + public async void Get_SmNotEnabled_Throws(SutProvider sutProvider, Guid data) + { + sutProvider.GetDependency().AccessSecretsManager(data).Returns(false); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetAsync(data)); + } + + [Theory] + [BitAutoData] + public async void Get_ThrowsNotFound(SutProvider sutProvider, Guid data) + { + sutProvider.GetDependency().AccessSecretsManager(data).Returns(true); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetAsync(data)); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void Get_Success(PermissionType permissionType, SutProvider sutProvider, + Guid orgId, Guid data) + { + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, orgId); + break; + case PermissionType.RunAsUserWithPermission: + SetupUserWithPermission(sutProvider, orgId); + sutProvider.GetDependency() + .UserHasReadAccessToProject(Arg.Is(data), Arg.Any()).ReturnsForAnyArgs(true); + break; + } + + sutProvider.GetDependency().GetByIdAsync(Arg.Is(data)) + .ReturnsForAnyArgs(new Project { Id = data, OrganizationId = orgId }); + + await sutProvider.Sut.GetAsync(data); + + await sutProvider.GetDependency().Received(1) + .GetByIdAsync(Arg.Is(data)); + } + + [Theory] + [BitAutoData] + public async void Get_UserWithoutPermission_Throws(SutProvider sutProvider, Guid orgId, + Guid data) + { + SetupUserWithPermission(sutProvider, orgId); + sutProvider.GetDependency().UserHasReadAccessToProject(Arg.Is(data), Arg.Any()) + .ReturnsForAnyArgs(false); + + sutProvider.GetDependency().GetByIdAsync(Arg.Is(data)) + .ReturnsForAnyArgs(new Project { Id = data, OrganizationId = orgId }); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetAsync(data)); + } + [Theory] [BitAutoData] public async void BulkDeleteProjects_Success(SutProvider sutProvider, List data) { sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - var ids = data.Select(project => project.Id)?.ToList(); - var mockResult = new List>(); - foreach (var project in data) - { - mockResult.Add(new Tuple(project, "")); - } + var ids = data.Select(project => project.Id).ToList(); + var mockResult = data.Select(project => new Tuple(project, "")).ToList(); + sutProvider.GetDependency().DeleteProjects(ids, default).ReturnsForAnyArgs(mockResult); var results = await sutProvider.Sut.BulkDeleteAsync(ids); await sutProvider.GetDependency().Received(1) - .DeleteProjects(Arg.Is(ids), Arg.Any()); + .DeleteProjects(Arg.Is(ids), Arg.Any()); Assert.Equal(data.Count, results.Data.Count()); } [Theory] [BitAutoData] - public async void BulkDeleteProjects_NoGuids_ThrowsArgumentNullException(SutProvider sutProvider) + public async void BulkDeleteProjects_NoGuids_ThrowsArgumentNullException( + SutProvider sutProvider) { sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); await Assert.ThrowsAsync(() => sutProvider.Sut.BulkDeleteAsync(new List())); diff --git a/test/Api.Test/SecretsManager/Enums/PermissionType.cs b/test/Api.Test/SecretsManager/Enums/PermissionType.cs new file mode 100644 index 0000000000..e411859fc8 --- /dev/null +++ b/test/Api.Test/SecretsManager/Enums/PermissionType.cs @@ -0,0 +1,7 @@ +namespace Bit.Api.Test.SecretsManager.Enums; + +public enum PermissionType +{ + RunAsAdmin, + RunAsUserWithPermission, +} From c24b08627572777ad9df0f9c017c6c2ebe139f38 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 13 Feb 2023 15:07:42 +0100 Subject: [PATCH 12/49] [SM-500] Upload sql diff for database validation (#2690) --- .github/workflows/database.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/database.yml b/.github/workflows/database.yml index 0e4a0171b3..4ef3aeaaeb 100644 --- a/.github/workflows/database.yml +++ b/.github/workflows/database.yml @@ -83,11 +83,17 @@ jobs: run: /usr/local/sqlpackage/sqlpackage /action:DeployReport /SourceFile:"Sql.dacpac" /TargetConnectionString:"Server=localhost;Database=vault_dev;User Id=SA;Password=SET_A_PASSWORD_HERE_123;Encrypt=True;TrustServerCertificate=True;" /OutputPath:"report.xml" /p:IgnoreColumnOrder=True /p:IgnoreComments=True shell: pwsh + - name: Generate SQL file + run: /usr/local/sqlpackage/sqlpackage /action:Script /SourceFile:"Sql.dacpac" /TargetConnectionString:"Server=localhost;Database=vault_dev;User Id=SA;Password=SET_A_PASSWORD_HERE_123;Encrypt=True;TrustServerCertificate=True;" /OutputPath:"diff.sql" /p:IgnoreColumnOrder=True /p:IgnoreComments=True + shell: pwsh + - name: Upload Report uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 with: name: report.xml - path: report.xml + path: | + report.xml + diff.sql - name: Validate XML run: | From 109d915d9e762aaff694835cf1d0146c153e0689 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 13 Feb 2023 18:10:53 +0100 Subject: [PATCH 13/49] Disable storage in tests (#2689) --- .../Factories/WebApplicationFactoryBase.cs | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs b/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs index d582d22cb5..e2546ac5a7 100644 --- a/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs +++ b/test/IntegrationTestCommon/Factories/WebApplicationFactoryBase.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using NoopRepos = Bit.Core.Repositories.Noop; namespace Bit.IntegrationTestCommon.Factories; @@ -50,7 +51,14 @@ public abstract class WebApplicationFactoryBase : WebApplicationFactory { "globalSettings:postgreSql:connectionString", "Host=localhost;Username=test;Password=test;Database=test" }, // Clear the redis connection string for distributed caching, forcing an in-memory implementation - { "globalSettings:redis:connectionString", ""} + { "globalSettings:redis:connectionString", ""}, + + // Clear Storage + { "globalSettings:attachment:connectionString", null}, + { "globalSettings:events:connectionString", null}, + { "globalSettings:send:connectionString", null}, + { "globalSettings:notifications:connectionString", null}, + { "globalSettings:storage:connectionString", null}, }); }); @@ -97,6 +105,28 @@ public abstract class WebApplicationFactoryBase : WebApplicationFactory services.Remove(captchaValidationService); services.AddSingleton(); + // Disable blocking + var blockingService = services.First(sd => sd.ServiceType == typeof(IBlockIpService)); + services.Remove(blockingService); + services.AddSingleton(); + + // TODO: Install and use azurite in CI pipeline + var installationDeviceRepository = + services.First(sd => sd.ServiceType == typeof(IInstallationDeviceRepository)); + services.Remove(installationDeviceRepository); + services.AddSingleton(); + + // TODO: Install and use azurite in CI pipeline + var metaDataRepository = + services.First(sd => sd.ServiceType == typeof(IMetaDataRepository)); + services.Remove(metaDataRepository); + services.AddSingleton(); + + // TODO: Install and use azurite in CI pipeline + var referenceEventService = services.First(sd => sd.ServiceType == typeof(IReferenceEventService)); + services.Remove(referenceEventService); + services.AddSingleton(); + // Our Rate limiter works so well that it begins to fail tests unless we carve out // one whitelisted ip. We should still test the rate limiter though and they should change the Ip // to something that is NOT whitelisted From 5836c87bb4f1bfdc64c47c2c987f6cd672a496b9 Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Tue, 14 Feb 2023 09:24:31 -0500 Subject: [PATCH 14/49] SM-365: Add Export & Import Functionality for SM (#2591) * SM-365: Add Export endpoint * SM-365: Add SM Import/Export support * SM-365: Fix DI and add temp NoAccessCheck * SM-365: Add access checks to import / export * SM-365: dotnet format * SM-365: Fix import bugs * SM-365: Fix import bug with EF & refactor based on PR comments * SM-365: Update access permissions in export * SM-365: Address PR comments * SM-365: Refactor for readability and PR comments --- .../Commands/Porting/ImportCommand.cs | 101 ++++++++++++++++++ .../SecretsManagerCollectionExtensions.cs | 3 + .../Repositories/ProjectRepository.cs | 10 ++ .../Repositories/SecretRepository.cs | 57 +++++++++- .../SecretsManagerPortingController.cs | 66 ++++++++++++ .../Models/Request/SMImportRequestModel.cs | 70 ++++++++++++ .../Models/Response/SMExportResponseModel.cs | 46 ++++++++ .../Models/Response/SMImportResponseModel.cs | 50 +++++++++ .../Porting/Interfaces/IImportCommand.cs | 7 ++ .../Commands/Porting/SMImport.cs | 41 +++++++ .../Repositories/IProjectRepository.cs | 1 + .../Repositories/ISecretRepository.cs | 2 + 12 files changed, 453 insertions(+), 1 deletion(-) create mode 100644 bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Porting/ImportCommand.cs create mode 100644 src/Api/SecretsManager/Controllers/SecretsManagerPortingController.cs create mode 100644 src/Api/SecretsManager/Models/Request/SMImportRequestModel.cs create mode 100644 src/Api/SecretsManager/Models/Response/SMExportResponseModel.cs create mode 100644 src/Api/SecretsManager/Models/Response/SMImportResponseModel.cs create mode 100644 src/Core/SecretsManager/Commands/Porting/Interfaces/IImportCommand.cs create mode 100644 src/Core/SecretsManager/Commands/Porting/SMImport.cs diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Porting/ImportCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Porting/ImportCommand.cs new file mode 100644 index 0000000000..9520f6f00f --- /dev/null +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Porting/ImportCommand.cs @@ -0,0 +1,101 @@ +using Bit.Core.SecretsManager.Commands.Porting; +using Bit.Core.SecretsManager.Commands.Porting.Interfaces; +using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Repositories; + +namespace Bit.Commercial.Core.SecretsManager.Commands.Porting; + +public class ImportCommand : IImportCommand +{ + private readonly IProjectRepository _projectRepository; + private readonly ISecretRepository _secretRepository; + + public ImportCommand(IProjectRepository projectRepository, ISecretRepository secretRepository) + { + _projectRepository = projectRepository; + _secretRepository = secretRepository; + } + + public async Task ImportAsync(Guid organizationId, SMImport import) + { + var importedProjects = new List(); + var importedSecrets = new List(); + + try + { + import = AssignNewIds(import); + + if (import.Projects.Any()) + { + importedProjects = (await _projectRepository.ImportAsync(import.Projects.Select(p => new Project + { + Id = p.Id, + OrganizationId = organizationId, + Name = p.Name, + }))).Select(p => p.Id).ToList(); + } + + if (import.Secrets != null && import.Secrets.Any()) + { + importedSecrets = (await _secretRepository.ImportAsync(import.Secrets.Select(s => new Secret + { + Id = s.Id, + OrganizationId = organizationId, + Key = s.Key, + Value = s.Value, + Note = s.Note, + Projects = s.ProjectIds?.Select(id => new Project { Id = id }).ToList(), + }))).Select(s => s.Id).ToList(); + } + } + catch (Exception) + { + if (importedProjects.Any()) + { + await _projectRepository.DeleteManyByIdAsync(importedProjects); + } + + if (importedSecrets.Any()) + { + await _secretRepository.HardDeleteManyByIdAsync(importedSecrets); + } + + throw new Exception("Error attempting import"); + } + } + + public SMImport AssignNewIds(SMImport import) + { + var projects = new Dictionary(); + var secrets = new List(); + + if (import.Projects != null && import.Projects.Any()) + { + projects = import.Projects.ToDictionary( + p => p.Id, + p => new SMImport.InnerProject { Id = Guid.NewGuid(), Name = p.Name } + ); + } + + if (import.Secrets != null && import.Secrets.Any()) + { + foreach (var secret in import.Secrets) + { + secrets.Add(new SMImport.InnerSecret + { + Id = Guid.NewGuid(), + Key = secret.Key, + Value = secret.Value, + Note = secret.Note, + ProjectIds = secret.ProjectIds?.Select(id => projects[id].Id), + }); + } + } + + return new SMImport + { + Projects = projects.Values, + Secrets = secrets, + }; + } +} diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs index dc645d1a1e..81ef53454f 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs @@ -1,10 +1,12 @@ using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; using Bit.Commercial.Core.SecretsManager.Commands.AccessTokens; +using Bit.Commercial.Core.SecretsManager.Commands.Porting; using Bit.Commercial.Core.SecretsManager.Commands.Projects; using Bit.Commercial.Core.SecretsManager.Commands.Secrets; using Bit.Commercial.Core.SecretsManager.Commands.ServiceAccounts; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Commands.AccessTokens.Interfaces; +using Bit.Core.SecretsManager.Commands.Porting.Interfaces; using Bit.Core.SecretsManager.Commands.Projects.Interfaces; using Bit.Core.SecretsManager.Commands.Secrets.Interfaces; using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces; @@ -28,5 +30,6 @@ public static class SecretsManagerCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); } } diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs index bf35a47dc2..fac888e18e 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs @@ -103,4 +103,14 @@ public class ProjectRepository : Repository> ImportAsync(IEnumerable projects) + { + using var scope = ServiceScopeFactory.CreateScope(); + var entities = projects.Select(p => Mapper.Map(p)); + var dbContext = GetDatabaseContext(scope); + await GetDbSet(dbContext).AddRangeAsync(entities); + await dbContext.SaveChangesAsync(); + return projects; + } } diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs index a17af71e60..1bd02992c3 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs @@ -92,7 +92,6 @@ public class SecretRepository : Repository UpdateAsync(Core.SecretsManager.Entities.Secret secret) { - using (var scope = ServiceScopeFactory.CreateScope()) { var dbContext = GetDatabaseContext(scope); @@ -136,4 +135,60 @@ public class SecretRepository : Repository ids) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var utcNow = DateTime.UtcNow; + var secrets = dbContext.Secret.Where(c => ids.Contains(c.Id)); + await secrets.ForEachAsync(secret => + { + dbContext.Attach(secret); + dbContext.Remove(secret); + }); + await dbContext.SaveChangesAsync(); + } + } + + public async Task> ImportAsync(IEnumerable secrets) + { + try + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var entities = new List(); + var projects = secrets + .SelectMany(s => s.Projects ?? Enumerable.Empty()) + .DistinctBy(p => p.Id) + .Select(p => Mapper.Map(p)) + .ToDictionary(p => p.Id, p => p); + + dbContext.AttachRange(projects); + + foreach (var s in secrets) + { + var entity = Mapper.Map(s); + + if (s.Projects?.Count > 0) + { + entity.Projects = s.Projects.Select(p => projects[p.Id]).ToList(); + } + + entities.Add(entity); + } + await GetDbSet(dbContext).AddRangeAsync(entities); + await dbContext.SaveChangesAsync(); + } + return secrets; + } + catch (Exception e) + { + Console.WriteLine(e); + } + + return secrets; + } } diff --git a/src/Api/SecretsManager/Controllers/SecretsManagerPortingController.cs b/src/Api/SecretsManager/Controllers/SecretsManagerPortingController.cs new file mode 100644 index 0000000000..e85ace3b61 --- /dev/null +++ b/src/Api/SecretsManager/Controllers/SecretsManagerPortingController.cs @@ -0,0 +1,66 @@ +using Bit.Api.SecretsManager.Models.Request; +using Bit.Api.SecretsManager.Models.Response; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.SecretsManager.Commands.Porting.Interfaces; +using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.Api.SecretsManager.Controllers; + +[SecretsManager] +public class SecretsManagerPortingController : Controller +{ + private readonly ISecretRepository _secretRepository; + private readonly IProjectRepository _projectRepository; + private readonly IUserService _userService; + private readonly IImportCommand _importCommand; + private readonly ICurrentContext _currentContext; + + public SecretsManagerPortingController(ISecretRepository secretRepository, IProjectRepository projectRepository, IUserService userService, IImportCommand importCommand, ICurrentContext currentContext) + { + _secretRepository = secretRepository; + _projectRepository = projectRepository; + _userService = userService; + _importCommand = importCommand; + _currentContext = currentContext; + } + + [HttpGet("sm/{organizationId}/export")] + public async Task Export([FromRoute] Guid organizationId, [FromRoute] string format = "json") + { + if (!await _currentContext.OrganizationAdmin(organizationId)) + { + throw new UnauthorizedAccessException(); + } + + var userId = _userService.GetProperUserId(User).Value; + var projects = await _projectRepository.GetManyByOrganizationIdAsync(organizationId, userId, AccessClientType.NoAccessCheck); + var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId); + + if (projects == null && secrets == null) + { + throw new NotFoundException(); + } + + return new SMExportResponseModel(projects, secrets); + } + + [HttpPost("sm/{organizationId}/import")] + public async Task Import([FromRoute] Guid organizationId, [FromBody] SMImportRequestModel importRequest) + { + if (!await _currentContext.OrganizationAdmin(organizationId)) + { + throw new UnauthorizedAccessException(); + } + + if (importRequest.Projects?.Count() > 1000 || importRequest.Secrets?.Count() > 6000) + { + throw new BadRequestException("You cannot import this much data at once, the limit is 1000 projects and 6000 secrets."); + } + + await _importCommand.ImportAsync(organizationId, importRequest.ToSMImport()); + } +} diff --git a/src/Api/SecretsManager/Models/Request/SMImportRequestModel.cs b/src/Api/SecretsManager/Models/Request/SMImportRequestModel.cs new file mode 100644 index 0000000000..13cc3b6f73 --- /dev/null +++ b/src/Api/SecretsManager/Models/Request/SMImportRequestModel.cs @@ -0,0 +1,70 @@ +using System.ComponentModel.DataAnnotations; +using Bit.Core.SecretsManager.Commands.Porting; +using Bit.Core.Utilities; + +namespace Bit.Api.SecretsManager.Models.Request; + +public class SMImportRequestModel +{ + public IEnumerable Projects { get; set; } + public IEnumerable Secrets { get; set; } + + public class InnerProjectImportRequestModel + { + public InnerProjectImportRequestModel() { } + + [Required] + public Guid Id { get; set; } + + [Required] + [EncryptedString] + [EncryptedStringLength(1000)] + public string Name { get; set; } + } + + public class InnerSecretImportRequestModel + { + public InnerSecretImportRequestModel() { } + + [Required] + public Guid Id { get; set; } + + [Required] + [EncryptedString] + [EncryptedStringLength(1000)] + public string Key { get; set; } + + [Required] + [EncryptedString] + [EncryptedStringLength(1000)] + public string Value { get; set; } + + [Required] + [EncryptedString] + [EncryptedStringLength(1000)] + public string Note { get; set; } + + [Required] + public IEnumerable ProjectIds { get; set; } + } + + public SMImport ToSMImport() + { + return new SMImport + { + Projects = Projects?.Select(p => new SMImport.InnerProject + { + Id = p.Id, + Name = p.Name, + }), + Secrets = Secrets?.Select(s => new SMImport.InnerSecret + { + Id = s.Id, + Key = s.Key, + Value = s.Value, + Note = s.Note, + ProjectIds = s.ProjectIds, + }), + }; + } +} diff --git a/src/Api/SecretsManager/Models/Response/SMExportResponseModel.cs b/src/Api/SecretsManager/Models/Response/SMExportResponseModel.cs new file mode 100644 index 0000000000..6d83117c32 --- /dev/null +++ b/src/Api/SecretsManager/Models/Response/SMExportResponseModel.cs @@ -0,0 +1,46 @@ +using Bit.Core.Models.Api; +using Bit.Core.SecretsManager.Entities; + +namespace Bit.Api.SecretsManager.Models.Response; + +public class SMExportResponseModel : ResponseModel +{ + public SMExportResponseModel(IEnumerable projects, IEnumerable secrets, string obj = "SecretsManagerExportResponseModel") : base(obj) + { + Secrets = secrets?.Select(s => new InnerSecretExportResponseModel(s)); + Projects = projects?.Select(p => new InnerProjectExportResponseModel(p)); + } + + public IEnumerable Projects { get; set; } + public IEnumerable Secrets { get; set; } + + public class InnerProjectExportResponseModel + { + public InnerProjectExportResponseModel(Project project) + { + Id = project.Id; + Name = project.Name; + } + + public Guid Id { get; set; } + public string Name { get; set; } + } + + public class InnerSecretExportResponseModel + { + public InnerSecretExportResponseModel(Secret secret) + { + Id = secret.Id; + Key = secret.Key; + Value = secret.Value; + Note = secret.Note; + ProjectIds = secret.Projects?.Select(p => p.Id); + } + + public Guid Id { get; set; } + public string Key { get; set; } + public string Value { get; set; } + public string Note { get; set; } + public IEnumerable ProjectIds { get; set; } + } +} diff --git a/src/Api/SecretsManager/Models/Response/SMImportResponseModel.cs b/src/Api/SecretsManager/Models/Response/SMImportResponseModel.cs new file mode 100644 index 0000000000..25d9956c43 --- /dev/null +++ b/src/Api/SecretsManager/Models/Response/SMImportResponseModel.cs @@ -0,0 +1,50 @@ +using Bit.Core.Models.Api; +using Bit.Core.SecretsManager.Commands.Porting; + +namespace Bit.Api.SecretsManager.Models.Response; + +public class SMImportResponseModel : ResponseModel +{ + public SMImportResponseModel(SMImport import, string obj = "SecretsManagerImportResponseModel") : base(obj) + { + Projects = import.Projects?.Select(p => new InnerProjectImportResponseModel(p)); + Secrets = import.Secrets?.Select(s => new InnerSecretImportResponseModel(s)); + } + + public IEnumerable Projects { get; set; } + public IEnumerable Secrets { get; set; } + + public class InnerProjectImportResponseModel + { + public InnerProjectImportResponseModel() { } + + public InnerProjectImportResponseModel(SMImport.InnerProject project) + { + Id = project.Id; + Name = project.Name; + } + + public Guid Id { get; set; } + public string Name { get; set; } + } + + public class InnerSecretImportResponseModel + { + public InnerSecretImportResponseModel() { } + + public InnerSecretImportResponseModel(SMImport.InnerSecret secret) + { + Id = secret.Id; + Key = secret.Key; + Value = secret.Value; + Note = secret.Note; + ProjectIds = secret.ProjectIds; + } + + public Guid Id { get; set; } + public string Key { get; set; } + public string Value { get; set; } + public string Note { get; set; } + public IEnumerable ProjectIds { get; set; } + } +} diff --git a/src/Core/SecretsManager/Commands/Porting/Interfaces/IImportCommand.cs b/src/Core/SecretsManager/Commands/Porting/Interfaces/IImportCommand.cs new file mode 100644 index 0000000000..7950e1a17c --- /dev/null +++ b/src/Core/SecretsManager/Commands/Porting/Interfaces/IImportCommand.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.SecretsManager.Commands.Porting.Interfaces; + +public interface IImportCommand +{ + Task ImportAsync(Guid organizationId, SMImport import); + SMImport AssignNewIds(SMImport import); +} diff --git a/src/Core/SecretsManager/Commands/Porting/SMImport.cs b/src/Core/SecretsManager/Commands/Porting/SMImport.cs new file mode 100644 index 0000000000..0e61b3acaf --- /dev/null +++ b/src/Core/SecretsManager/Commands/Porting/SMImport.cs @@ -0,0 +1,41 @@ +namespace Bit.Core.SecretsManager.Commands.Porting; + +public class SMImport +{ + public IEnumerable Projects { get; set; } + public IEnumerable Secrets { get; set; } + + public class InnerProject + { + public InnerProject() { } + + public InnerProject(Core.SecretsManager.Entities.Project project) + { + Id = project.Id; + Name = project.Name; + } + + public Guid Id { get; set; } + public string Name { get; set; } + } + + public class InnerSecret + { + public InnerSecret() { } + + public InnerSecret(Core.SecretsManager.Entities.Secret secret) + { + Id = secret.Id; + Key = secret.Key; + Value = secret.Value; + Note = secret.Note; + ProjectIds = secret.Projects != null && secret.Projects.Any() ? secret.Projects.Select(p => p.Id) : null; + } + + public Guid Id { get; set; } + public string Key { get; set; } + public string Value { get; set; } + public string Note { get; set; } + public IEnumerable ProjectIds { get; set; } + } +} diff --git a/src/Core/SecretsManager/Repositories/IProjectRepository.cs b/src/Core/SecretsManager/Repositories/IProjectRepository.cs index ee752f5455..b742b811c6 100644 --- a/src/Core/SecretsManager/Repositories/IProjectRepository.cs +++ b/src/Core/SecretsManager/Repositories/IProjectRepository.cs @@ -11,6 +11,7 @@ public interface IProjectRepository Task CreateAsync(Project project); Task ReplaceAsync(Project project); Task DeleteManyByIdAsync(IEnumerable ids); + Task> ImportAsync(IEnumerable projects); Task UserHasReadAccessToProject(Guid id, Guid userId); Task UserHasWriteAccessToProject(Guid id, Guid userId); } diff --git a/src/Core/SecretsManager/Repositories/ISecretRepository.cs b/src/Core/SecretsManager/Repositories/ISecretRepository.cs index 0b96def9f7..56f4dc8662 100644 --- a/src/Core/SecretsManager/Repositories/ISecretRepository.cs +++ b/src/Core/SecretsManager/Repositories/ISecretRepository.cs @@ -11,4 +11,6 @@ public interface ISecretRepository Task CreateAsync(Secret secret); Task UpdateAsync(Secret secret); Task SoftDeleteManyByIdAsync(IEnumerable ids); + Task HardDeleteManyByIdAsync(IEnumerable ids); + Task> ImportAsync(IEnumerable secrets); } From ee03ad4158f2ce114fa13a89bdfdf6146a8edcc7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Feb 2023 10:30:49 -0500 Subject: [PATCH 15/49] Bumped version to 2023.2.0 (#2693) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index ce11b46f50..822a4f48d2 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ net6.0 - 2023.1.0 + 2023.2.0 Bit.$(MSBuildProjectName) true enable From 5aa8f3db8104d8d3dfe308a4afef2fb255ffb30a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?= Date: Tue, 14 Feb 2023 17:41:59 +0100 Subject: [PATCH 16/49] [DEVOPS-1161] fix the rate limiting issue in building bitwarden unified (#2694) * Add GH_PAT secret to build * Fix secret * Fix * Fix * Maybe fix * add cat for tags.json * Maybe fix * Matbe fix * Trying to fix * Change gh_pat path * Fix * Remove obsolete tags obtaining --- .github/workflows/build-self-host.yml | 13 +++++++++++-- docker-unified/Dockerfile | 8 +++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-self-host.yml b/.github/workflows/build-self-host.yml index e4ccb2eb2d..39fa3a0f43 100644 --- a/.github/workflows/build-self-host.yml +++ b/.github/workflows/build-self-host.yml @@ -48,11 +48,17 @@ jobs: run: az acr login -n bitwardenqa - name: Login to Azure - Prod Subscription - if: ${{ env.is_publish_branch == 'true' }} uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} + - name: Retrieve github PAT secrets + id: retrieve-secret-pat + uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af + with: + keyvault: "bitwarden-prod-kv" + secrets: "github-pat-bitwarden-devops-bot-repo-scope" + - name: Retrieve secrets if: ${{ env.is_publish_branch == 'true' }} id: retrieve-secrets @@ -62,7 +68,8 @@ jobs: secrets: "docker-password, docker-username, dct-delegate-2-repo-passphrase, - dct-delegate-2-key" + dct-delegate-2-key + github-pat-bitwarden-devops-bot-repo-scope" - name: Log into Docker if: ${{ env.is_publish_branch == 'true' }} @@ -118,6 +125,8 @@ jobs: linux/arm64/v8 push: true tags: ${{ steps.tag-list.outputs.tags }} + secrets: | + "GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}" - name: Log out of Docker and disable Docker Notary if: ${{ env.is_publish_branch == 'true' }} diff --git a/docker-unified/Dockerfile b/docker-unified/Dockerfile index 110ca7991b..04952c3d1c 100644 --- a/docker-unified/Dockerfile +++ b/docker-unified/Dockerfile @@ -1,3 +1,4 @@ +# syntax = docker/dockerfile:1.2 ############################################### # Build stage # ############################################### @@ -13,7 +14,12 @@ RUN apt-get update && apt-get install -y \ WORKDIR /tmp # Download tags from 'clients' repository -RUN curl https://api.github.com/repos/bitwarden/clients/git/refs/tags --output tags.json +RUN --mount=type=secret,id=GH_PAT,target=/etc/secrets/GH_PAT if [ -e "/etc/secrets/GH_PAT" ]; then \ +curl --header "Authorization: token $(cat /etc/secrets/GH_PAT)" \ + https://api.github.com/repos/bitwarden/clients/git/refs/tags --output tags.json ; else \ + curl https://api.github.com/repos/bitwarden/clients/git/refs/tags --output tags.json ; fi + +RUN cat tags.json # Grab last tag/release of the 'web' client RUN cat tags.json | jq -r 'last(.[] | select(.ref|test("refs/tags/web-v[0-9]{4}.[0-9]{1,2}.[0-9]+"))) | .ref | split("/")[2]' > tag.txt From 8138db396b22a245a43b341fe493f32a61252171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?= Date: Wed, 15 Feb 2023 11:45:27 +0100 Subject: [PATCH 17/49] Fix secrets retrieval in build-self-host workflow (#2696) --- .github/workflows/build-self-host.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build-self-host.yml b/.github/workflows/build-self-host.yml index 39fa3a0f43..f8e2ec07f8 100644 --- a/.github/workflows/build-self-host.yml +++ b/.github/workflows/build-self-host.yml @@ -68,8 +68,7 @@ jobs: secrets: "docker-password, docker-username, dct-delegate-2-repo-passphrase, - dct-delegate-2-key - github-pat-bitwarden-devops-bot-repo-scope" + dct-delegate-2-key" - name: Log into Docker if: ${{ env.is_publish_branch == 'true' }} From b44ff27d3a690369dd9cd91580c7f1d1e521c69b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?= Date: Wed, 15 Feb 2023 11:45:46 +0100 Subject: [PATCH 18/49] [DEVOPS-1211] Push docker only to bitwardenprod ACR (#2695) * Push docker only to prod ACR * Remove matrix in container registry purge --- .github/workflows/build-self-host.yml | 6 +- .github/workflows/build.yml | 53 +++++---------- .github/workflows/cleanup-after-pr.yml | 8 +-- .../workflows/container-registry-purge.yml | 15 +---- .github/workflows/release.yml | 64 ++----------------- 5 files changed, 31 insertions(+), 115 deletions(-) diff --git a/.github/workflows/build-self-host.yml b/.github/workflows/build-self-host.yml index f8e2ec07f8..090e8fb8a7 100644 --- a/.github/workflows/build-self-host.yml +++ b/.github/workflows/build-self-host.yml @@ -45,7 +45,7 @@ jobs: creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }} - name: Login to Azure ACR - run: az acr login -n bitwardenqa + run: az acr login -n bitwardenprod - name: Login to Azure - Prod Subscription uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf @@ -108,9 +108,9 @@ jobs: IMAGE_TAG: ${{ steps.tag.outputs.image_tag }} run: | if [ "$IMAGE_TAG" = "dev" ] || [ "$IMAGE_TAG" = "beta" ]; then - echo "tags=bitwardenqa.azurecr.io/self-host:${IMAGE_TAG},bitwarden/self-host:${IMAGE_TAG}" >> $GITHUB_OUTPUT + echo "tags=bitwardenprod.azurecr.io/self-host:${IMAGE_TAG},bitwarden/self-host:${IMAGE_TAG}" >> $GITHUB_OUTPUT else - echo "tags=bitwardenqa.azurecr.io/self-host:${IMAGE_TAG}" >> $GITHUB_OUTPUT + echo "tags=bitwardenprod.azurecr.io/self-host:${IMAGE_TAG}" >> $GITHUB_OUTPUT fi - name: Build Docker image diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9845c2ed2a..bc2cec89d1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -191,65 +191,65 @@ jobs: include: - project_name: Admin base_path: ./src - docker_repos: [bitwarden, bitwardenqa.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io] dotnet: true - project_name: Api base_path: ./src - docker_repos: [bitwarden, bitwardenqa.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io] dotnet: true - project_name: Attachments base_path: ./util - docker_repos: [bitwarden, bitwardenqa.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io] - project_name: Events base_path: ./src - docker_repos: [bitwarden, bitwardenqa.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io] dotnet: true - project_name: EventsProcessor base_path: ./src - docker_repos: [bitwardenqa.azurecr.io] + docker_repos: [bitwardenprod.azurecr.io] dotnet: true - project_name: Icons base_path: ./src - docker_repos: [bitwarden, bitwardenqa.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io] dotnet: true - project_name: Identity base_path: ./src - docker_repos: [bitwarden, bitwardenqa.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io] dotnet: true - project_name: MsSql base_path: ./util - docker_repos: [bitwarden, bitwardenqa.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io] - project_name: Nginx base_path: ./util - docker_repos: [bitwarden, bitwardenqa.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io] - project_name: Notifications base_path: ./src - docker_repos: [bitwarden, bitwardenqa.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io] dotnet: true - project_name: Server base_path: ./util - docker_repos: [bitwarden, bitwardenqa.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io] dotnet: true - project_name: Setup base_path: ./util - docker_repos: [bitwarden, bitwardenqa.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io] dotnet: true - project_name: Sso base_path: ./bitwarden_license/src - docker_repos: [bitwarden, bitwardenqa.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io] dotnet: true - project_name: Scim base_path: ./bitwarden_license/src - docker_repos: [bitwarden, bitwardenqa.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io] dotnet: true - project_name: Billing base_path: ./src - docker_repos: [bitwardenqa.azurecr.io] + docker_repos: [bitwardenprod.azurecr.io] dotnet: true steps: - name: Checkout repo uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 - + - name: Set up image tag run: | IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name @@ -285,27 +285,6 @@ jobs: PROJECT_NAME: ${{ steps.setup.outputs.project_name }} run: docker build -t $PROJECT_NAME ${{ matrix.base_path }}/${{ matrix.project_name }} - ########## QA ACR ########## - - name: Login to Azure - QA Subscription - uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf - with: - creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }} - - - name: Login to QA ACR - run: az acr login -n bitwardenqa - - - name: Tag and push image to QA ACR - env: - PROJECT_NAME: ${{ steps.setup.outputs.project_name }} - REGISTRY: bitwardenqa.azurecr.io - run: | - docker tag $PROJECT_NAME \ - $REGISTRY/$PROJECT_NAME:${{ env.IMAGE_TAG }} - docker push $REGISTRY/$PROJECT_NAME:${{ env.IMAGE_TAG }} - - - name: Log out of Docker - run: docker logout - ########## PROD ACR ########## - name: Login to Azure - PROD Subscription uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf diff --git a/.github/workflows/cleanup-after-pr.yml b/.github/workflows/cleanup-after-pr.yml index 696d84c8f4..5fc34700fb 100644 --- a/.github/workflows/cleanup-after-pr.yml +++ b/.github/workflows/cleanup-after-pr.yml @@ -14,18 +14,18 @@ jobs: uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f ########## ACR ########## - - name: Login to Azure - QA Subscription + - name: Login to Azure - PROD Subscription uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a with: - creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }} + creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} - name: Login to Azure ACR - run: az acr login -n bitwardenqa + run: az acr login -n bitwardenprod ########## Remove Docker images ########## - name: Remove the docker image from ACR env: - REGISTRY_NAME: bitwardenqa + REGISTRY_NAME: bitwardenprod SERVICES: | services: - Admin diff --git a/.github/workflows/container-registry-purge.yml b/.github/workflows/container-registry-purge.yml index 8c9db2cbbd..b00b627809 100644 --- a/.github/workflows/container-registry-purge.yml +++ b/.github/workflows/container-registry-purge.yml @@ -11,28 +11,15 @@ jobs: purge: name: Purge old images runs-on: ubuntu-20.04 - strategy: - fail-fast: false - matrix: - include: - - name: bitwardenqa - - name: bitwardenprod steps: - name: Login to Azure - if: matrix.name == 'bitwardenprod' uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} - - name: Login to Azure - if: matrix.name == 'bitwardenqa' - uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf - with: - creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }} - - name: Purge images env: - REGISTRY: ${{ matrix.name }} + REGISTRY: bitwardenprod AGO_DUR_VER: "180d" AGO_DUR: "30d" run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 70f46ab4c7..fb1e5adc6d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -188,7 +188,7 @@ jobs: origin_docker_repo: bitwarden - project_name: EventsProcessor prod_acr: true - origin_docker_repo: bitwardenqa.azurecr.io + origin_docker_repo: bitwardenprod.azurecr.io - project_name: Icons origin_docker_repo: bitwarden prod_acr: true @@ -209,7 +209,7 @@ jobs: - project_name: Scim origin_docker_repo: bitwarden - project_name: Billing - origin_docker_repo: bitwardenqa.azurecr.io + origin_docker_repo: bitwardenprod.azurecr.io steps: - name: Print environment env: @@ -277,31 +277,19 @@ jobs: docker logout echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV - ########## ACR QA ########## - - name: Login to Azure - QA Subscription + ########## ACR PROD ########## + - name: Login to Azure - PROD Subscription uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a with: - creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }} + creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} - name: Login to Azure ACR - run: az acr login -n bitwardenqa - - - name: Pull latest project image - if: matrix.origin_docker_repo == 'bitwardenqa.azurecr.io' - env: - PROJECT_NAME: ${{ steps.setup.outputs.project_name }} - REGISTRY: bitwardenqa.azurecr.io - run: | - if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then - docker pull $REGISTRY/$PROJECT_NAME:latest - else - docker pull $REGISTRY/$PROJECT_NAME:$_BRANCH_NAME - fi + run: az acr login -n bitwardenprod - name: Tag version and latest env: PROJECT_NAME: ${{ steps.setup.outputs.project_name }} - REGISTRY: bitwardenqa.azurecr.io + REGISTRY: bitwardenprod.azurecr.io ORIGIN_REGISTRY: ${{ matrix.origin_docker_repo }} run: | if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then @@ -313,43 +301,6 @@ jobs: - name: Push version and latest image if: ${{ github.event.inputs.release_type != 'Dry Run' }} - env: - PROJECT_NAME: ${{ steps.setup.outputs.project_name }} - REGISTRY: bitwardenqa.azurecr.io - run: | - docker push $REGISTRY/$PROJECT_NAME:latest - docker push $REGISTRY/$PROJECT_NAME:$_RELEASE_VERSION - - - name: Log out of Docker - run: docker logout - - ########## ACR PROD ########## - - name: Login to Azure - PROD Subscription - if: matrix.prod_acr == true - uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a - with: - creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} - - - name: Login to Azure ACR - if: matrix.prod_acr == true - run: az acr login -n bitwardenprod - - - name: Tag version and latest - if: matrix.prod_acr == true - env: - PROJECT_NAME: ${{ steps.setup.outputs.project_name }} - REGISTRY: bitwardenprod.azurecr.io - ORIGIN_REGISTRY: ${{ matrix.origin_docker_repo }} - run: | - if [[ "${{ github.event.inputs.release_type }}" == "Dry Run" ]]; then - docker tag $ORIGIN_REGISTRY/$PROJECT_NAME:latest $REGISTRY/$PROJECT_NAME:dryrun - else - docker tag $ORIGIN_REGISTRY/$PROJECT_NAME:$_BRANCH_NAME $REGISTRY/$PROJECT_NAME:$_RELEASE_VERSION - docker tag $ORIGIN_REGISTRY/$PROJECT_NAME:$_BRANCH_NAME $REGISTRY/$PROJECT_NAME:latest - fi - - - name: Push version and latest image - if: ${{ github.event.inputs.release_type != 'Dry Run' && matrix.prod_acr == true }} env: PROJECT_NAME: ${{ steps.setup.outputs.project_name }} REGISTRY: bitwardenprod.azurecr.io @@ -358,7 +309,6 @@ jobs: docker push $REGISTRY/$PROJECT_NAME:latest - name: Log out of Docker - if: matrix.prod_acr == true run: docker logout release: From 70a7108bbaa6a1f94f139849f29a643041acd239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?= Date: Wed, 15 Feb 2023 17:03:51 +0100 Subject: [PATCH 19/49] Fix Azure login in build self host workflow (#2700) --- .github/workflows/build-self-host.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-self-host.yml b/.github/workflows/build-self-host.yml index 090e8fb8a7..261c5c91a1 100644 --- a/.github/workflows/build-self-host.yml +++ b/.github/workflows/build-self-host.yml @@ -39,19 +39,14 @@ jobs: uses: docker/setup-buildx-action@8c0edbc76e98fa90f69d9a2c020dcb50019dc325 ########## Login to Docker registries ########## - - name: Login to Azure - QA Subscription - uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf - with: - creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }} - - - name: Login to Azure ACR - run: az acr login -n bitwardenprod - - - name: Login to Azure - Prod Subscription + - name: Login to Azure - PROD Subscription uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} + - name: Login to Azure ACR + run: az acr login -n bitwardenprod + - name: Retrieve github PAT secrets id: retrieve-secret-pat uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af From 9261fcc9eec905e4893bc7099f15f7dc63e59b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?= Date: Wed, 15 Feb 2023 17:04:03 +0100 Subject: [PATCH 20/49] Add pull request trigger to build self-host workflow (#2701) --- .github/workflows/build-self-host.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build-self-host.yml b/.github/workflows/build-self-host.yml index 261c5c91a1..8d2a6ec498 100644 --- a/.github/workflows/build-self-host.yml +++ b/.github/workflows/build-self-host.yml @@ -9,6 +9,12 @@ on: paths-ignore: - ".github/workflows/**" workflow_dispatch: + pull_request: + branches-ignore: + - "l10n_master" + - "gh-pages" + paths: + - ".github/workflows/build-self-host.yml" jobs: build-docker: From 9a484bec07d55e51aa46a22ed3539e71fa317887 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Wed, 15 Feb 2023 14:26:41 -0500 Subject: [PATCH 21/49] [SG-147] Organization Domain Claiming Feature (#2704) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [SG-696] Organization Domain Claiming DB Objects and Migrations (#2394) * model organization domain claiming * Added migration scripts and db objects for mssql * create and implement sql repository abstraction * Added ef migrations for mysql and postgres. Removed time without timezone in previous migration * made update on sql migration to use create or alter statement * removed active column from OrganizationDomain table and decided to go with the hard delete approach * Ran dotnet restore evaluate * created DNS service verification using DNSClient (#2401) * [SG-678] Api Endpoints for Domain Claiming (#2430) * Added stored procedure to read claimed domains * Updated Organization Domain Repository to include method to get claimed domains * Updated domain entity and added request model * Implemented organization domain respository and regsitered it in the various extensions * Added create endpoint, request, responses and command * Added endpoint to get domain by domain entry id * Ran lint fix * Added new stored procedure to get domains by organizattion id * Moved migration scripts to init migration and added new procedure * Renamed from domainId to Id * Added and implemented GetDomainByOrganizationId * Completed GetDomainByOrgId endpoint and started work on verify domain endpoint * Updated the OrganizationDomain update procedure * Added delete command and include other endpoints in the controller * Remove test item from controller * Remove test item from controller * Changed access to allow admin, owners and manage sso roles * changed logic for setting the initial value for the NextRunCount * Renamed NextRunCount to JobRunCount * Renamed NextRunCount to JobRunCount on mysql * Renamed NextRunCount to JobRunCount on postgres * Removed chaining pattern and added logic to get next run date * Lint fix * Added stored procedure to get organization sso details by email address * Added endpoint to get sso details of an organization with email * Added organizationDomainRepository to OrganizationController test * merged with master and fixed conflicts * [SG-661] Background Domain Verification Service (#2455) * Added stored procedure to read claimed domains * Updated Organization Domain Repository to include method to get claimed domains * Updated domain entity and added request model * Implemented organization domain respository and regsitered it in the various extensions * Added create endpoint, request, responses and command * Added endpoint to get domain by domain entry id * Ran lint fix * Added new stored procedure to get domains by organizattion id * Moved migration scripts to init migration and added new procedure * Renamed from domainId to Id * Added and implemented GetDomainByOrganizationId * Completed GetDomainByOrgId endpoint and started work on verify domain endpoint * Updated the OrganizationDomain update procedure * Added delete command and include other endpoints in the controller * Remove test item from controller * Remove test item from controller * Changed access to allow admin, owners and manage sso roles * Added stored procedure to get unverified domains by nextrundate * Renamed stored procedure name * Added domain verification service interface * Added GetManyByNextRunDate to repository * Added verification domain service implementation * changed logic for setting the initial value for the NextRunCount * This commit should be signed using my SSH key * Renamed NextRunCount to JobRunCount * Renamed NextRunCount to JobRunCount on mysql * Renamed NextRunCount to JobRunCount on postgres * Removed chaining pattern and added logic to get next run date * Lint fix * Implemented EF core version on the repository * Created background job implementation and logic * popped stash * Updated stored procedure and EF script * Lint fix * Added logic to set next job count and the next run date when a verification is false * Added logic to set next job count and the next run date when a verification is false * Updated stored procedure name on repository * Removed test trigger * Lint fix * Added trigger for job * Added job count update after successful domain verification * Lint fix * Lint fix * [SG-682] Add Event Log Entries to Organization Domain (#2492) * Added domain name property to Event related objects * Added organization domain claiming event types * Created migration script and updated related event scripts to include domanName * Added EF Migrations * Renamed postres script file extension * Added DomainName property to response model * Added abstraction to interface * Added system name to enum * dotnet formattinfg fix * Added events to organization domain actions * Added LastCheckedDate property to domain * Migrations and stored procedure updates with new column * Added new stored procedure to get domain by org id and domain name * Log organization domain event abstract method * Ef migrattion to add new LastCheckedDate column * Added duplicate domain exception * Modified create command to include domain verification and last checked date and renamed methods used * removed variable * changed service lifetime * Renamed trigger * Initialed property in constructor * Ensured domain name is stored as lower case * Fixed suggestions from review * Fixed suggestions from review * Return Conflict Status on Organization Domain APIs (#2498) * Added conflict response to end point to help translate error message on the client better * Added conflict response to end point to help translate error message on the client better * Set message with exception message or generic message * Added last check date to response model (#2499) * Fix/Check to throw exception when domain is claimed by another organization (#2503) * Added check to ensure domain claimed by another organization cannot be verified * Made error message consistent * [SG-660] Organization Domain Maintenance (#2502) * Added email template * Mail service abstraction and implementation * Mail template model * Initial delete job commit * Added SPs to get all unverifed domains after 72 hours and another to delete unverified domains after 7 days * Moved all organization domain scripts to single file * Added new scripts implementation for sqlserver and EF core * Renamed service * Formatting fix * Added background service to send warning email and delete expired domains * Renamed variable * Added implementation for email warning to organization admins and for deleting expired domains after 7 days * Added formatting * Modified read if expired script to limit result to 4 days * Added send mail abstract method and implementation * Model used in build mail body * Completed maintenace service * Added comment to make logic clear * Fixed cron expression (#2505) * Modified procedure and methods to handle flexible verification adn expiration period (#2517) * Merged with master * [SG-908] Unit Tests for Organization Domain Claiming Feature (#2522) * added test controlleer class * added unit test for create command * Added query tests * Added tests for delete and verify command * Formated code and added some more unit tests * Fixed lint * Added log event assertion to create command tests * Added log event assertion to delete command tests * Added unit tests for organization domain controller * Added unit tests for organization domain service * Modified test after merge * fixed comment * fixed comment * fixed lint * Defect/SG-977 - Org domain event logs missing details (#2573) * SG-977 - (1) Refactor EventSystemUser.SSO to be EventSystemUser.DomainVerification to better match SCIM property and for easier display and translation on web client (2) Add new DeviceType of Server to be used on SCIM and Domain Verification logs so event log will show Server as client. * SG-977 - SCIM bugfix - Restoring / Revoking user access via Jumpcloud activation / suspension did not properly log the events as SCIM events so the client side showed Unknown for both Client and Member. * Run autoformat to fix lint errors * SG-977 - Fixed broken test due to new device type logic in event service * SG-976 - Add admin log and clean up log verbiage for domain verification (#2574) * SG-976 - Add admin log and clean up log verbiage for domain verification * SG-976 - (1) Use logInformation extension without exception (2) Clarify verbiage of logs * SG-955 - On domain verification error or failure, set last checked da… (#2541) * SG-955 - On domain verification error or failure, set last checked date on the org domain. * SG-955 - Refactoring VerifyOrganizationDomain event logging to avoid duplication and increase efficiency (based on Gbubemi's PR feedback) * Org Domain Background Verification service - set last checked date (#2599) * Refactored OrganizationDomain repository to work with latest changes on code base * Fixed formatting * [SG-957] Cannot Delete Organizations due to FK Constraint (#2602) * Added stored procedure to fix FX contstraint issue when deleting an organization * Update stored procedures related to organization delete with OrganizationDomain_OrganizationDelete SP * Fixed formatting * Updated SP * SG-990 - Log expired domains that are going to be deleted. * Fix lint errors with auto format * /home/runner/work/server/server/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs(107,2): error FINALNEWLINE: Fix final newline. Insert '\n'. * Added missing bracket to fix compile error. * Added imports for Domain Claiming classes that were lost on merge. * Fixing broken unit tests + adding proper behavior for newly added SCIM logic changing device type * Fix lint errors again * Included domain name set in constructor (#2618) * [SG-1001] Error Thrown When Verifying Sub Domains (#2621) * Renamed exception to a more generic name that receives error message from the dns client and also added updates to job count and next run date * Improved error logs by adding dns client error message * Fixed formatting * [SG-1001] Added event logs when a domain is not verified due to thrown exception (#2623) * Added eevent logs when a domain is not verified due to thrown exception * Fixed formatting * Org Domain Verification - Small refactor to improve method/model name… (#2641) * Org Domain Verification - Small refactor to improve method/model names and method locations - required refactoring of controller routes (I confirmed all behavior still functional) * Fixed organization test controller issue * Fixed lint * Autoformat org domain controller * Removing whitespace for lint argh, why does Rider not do this. --------- Co-authored-by: gbubemismith * Tweak name of Request model to match Response model for ClaimedOrgDomain call * [SG-1009] Users with Custom Role and "Manage SSO" permission don't receive verification failed email (#2645) * Modified condition to pick up unverified domains after said period * Fix to get emails of custom users with manage sso rights * Formatted code * Removed return that made background job exit on successful validation (#2648) * [SG-1014] Unit Tests for Get Organization Sso Details (#2655) * Added unit tests for GetOrgDomainSsoDetails * renamed variable * Adjust OrganizationDomainSsoDetails_ReadByEmail to use outer join so … (#2657) * Adjust OrganizationDomainSsoDetails_ReadByEmail to use outer join so that claimed domain results will come back if an org has not yet setup a policy * Removed migration as not needed * Updated OrganizationDomainSsoDetails_ReadByEmail from original creation migration to use outer join & handle null policy results (and still return results) * Fixed lint formatting --------- Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Co-authored-by: Jared Snider Co-authored-by: Todd Martin --- .../src/Commercial.Core/packages.lock.json | 9 + .../packages.lock.json | 9 + .../Scim/Controllers/v2/UsersController.cs | 4 +- bitwarden_license/src/Scim/packages.lock.json | 9 + bitwarden_license/src/Sso/packages.lock.json | 9 + .../Commercial.Core.Test/packages.lock.json | 9 + .../Scim.IntegrationTest/packages.lock.json | 9 + .../test/Scim.Test/packages.lock.json | 9 + perf/MicroBenchmarks/packages.lock.json | 9 + .../DeleteUnverifiedOrganizationDomainsJob.cs | 31 + src/Admin/Jobs/JobsHostedService.cs | 7 + src/Admin/packages.lock.json | 9 + .../OrganizationDomainController.cs | 143 ++ .../Controllers/OrganizationsController.cs | 31 +- src/Api/Jobs/JobsHostedService.cs | 10 +- src/Api/Jobs/ValidateOrganizationDomainJob.cs | 30 + .../Request/OrganizationDomainRequestModel.cs | 12 + ...rganizationDomainSsoDetailsRequestModel.cs | 10 + src/Api/Models/Response/EventResponseModel.cs | 2 + .../OrganizationDomainResponseModel.cs | 36 + ...ganizationDomainSsoDetailsResponseModel.cs | 28 + .../ExceptionHandlerFilterAttribute.cs | 5 + src/Api/packages.lock.json | 9 + src/Billing/packages.lock.json | 9 + src/Core/Core.csproj | 1 + src/Core/Entities/Event.cs | 3 + src/Core/Entities/OrganizationDomain.cs | 48 + src/Core/Enums/DeviceType.cs | 2 + src/Core/Enums/EventSystemUser.cs | 3 +- src/Core/Enums/EventType.cs | 5 + src/Core/Exceptions/ConflictException.cs | 6 +- src/Core/Exceptions/DnsQueryException.cs | 7 + src/Core/Exceptions/DomainClaimedException.cs | 10 + .../Exceptions/DomainVerifiedException.cs | 10 + .../Exceptions/DuplicateDomainException.cs | 10 + .../OrganizationDomainUnverified.html.hbs | 27 + .../OrganizationDomainUnverified.text.hbs | 10 + src/Core/Models/Data/EventMessage.cs | 1 + src/Core/Models/Data/EventTableEntity.cs | 2 + src/Core/Models/Data/IEvent.cs | 1 + .../OrganizationDomainSsoDetailsData.cs | 16 + .../OrganizationUserUserDetails.cs | 6 + .../OrganizationDomainUnverifiedViewModel.cs | 7 + .../CreateOrganizationDomainCommand.cs | 76 + .../DeleteOrganizationDomainCommand.cs | 32 + .../GetOrganizationDomainByIdQuery.cs | 18 + ...OrganizationDomainByOrganizationIdQuery.cs | 18 + .../ICreateOrganizationDomainCommand.cs | 8 + .../IDeleteOrganizationDomainCommand.cs | 6 + .../IGetOrganizationDomainByIdQuery.cs | 8 + ...OrganizationDomainByOrganizationIdQuery.cs | 8 + .../IVerifyOrganizationDomainCommand.cs | 8 + .../VerifyOrganizationDomainCommand.cs | 73 + ...OrganizationServiceCollectionExtensions.cs | 12 + .../IOrganizationDomainRepository.cs | 15 + src/Core/Services/IDnsResolverService.cs | 6 + src/Core/Services/IEventService.cs | 2 + src/Core/Services/IMailService.cs | 1 + .../Services/IOrganizationDomainService.cs | 7 + .../Implementations/DnsResolverService.cs | 21 + .../Services/Implementations/EventService.cs | 72 +- .../Implementations/HandlebarsMailService.cs | 13 + .../OrganizationDomainService.cs | 139 ++ .../NoopImplementations/NoopEventService.cs | 13 + .../NoopImplementations/NoopMailService.cs | 5 + src/Core/Settings/GlobalSettings.cs | 7 + .../Settings/IDomainVerificationSettings.cs | 7 + src/Core/Settings/IGlobalSettings.cs | 1 + src/Core/packages.lock.json | 9 + src/Events/packages.lock.json | 9 + src/EventsProcessor/packages.lock.json | 9 + src/Icons/packages.lock.json | 9 + src/Identity/packages.lock.json | 9 + .../DapperServiceCollectionExtensions.cs | 1 + .../OrganizationDomainRepository.cs | 110 ++ src/Infrastructure.Dapper/packages.lock.json | 9 + ...ityFrameworkServiceCollectionExtensions.cs | 1 + .../Models/Organization.cs | 1 + .../Models/OrganizationDomain.cs | 16 + .../Repositories/DatabaseContext.cs | 4 + .../OrganizationDomainRepository.cs | 147 ++ .../packages.lock.json | 9 + src/Notifications/packages.lock.json | 9 + .../Utilities/ServiceCollectionExtensions.cs | 2 + src/SharedWeb/packages.lock.json | 9 + src/Sql/Sql.sqlproj | 16 + .../dbo/Stored Procedures/Event_Create.sql | 9 +- ...ganizationDomainSsoDetails_ReadByEmail.sql | 29 + .../OrganizationDomain_Create.sql | 39 + .../OrganizationDomain_DeleteById.sql | 12 + .../OrganizationDomain_DeleteIfExpired.sql | 10 + ...OrganizationDomain_OrganizationDeleted.sql | 12 + ...OrganizationDomain_ReadByClaimedDomain.sql | 15 + .../OrganizationDomain_ReadById.sql | 13 + .../OrganizationDomain_ReadByNextRunDate.sql | 25 + ...rganizationDomain_ReadByOrganizationId.sql | 13 + ...nDomain_ReadDomainByOrgIdAndDomainName.sql | 16 + .../OrganizationDomain_ReadIfExpired.sql | 13 + .../OrganizationDomain_Update.sql | 28 + .../Organization_DeleteById.sql | 1 + src/Sql/dbo/Tables/Event.sql | 1 + src/Sql/dbo/Tables/OrganizationDomain.sql | 15 + src/Sql/dbo/Views/OrganizationDomainView.sql | 6 + test/Api.IntegrationTest/packages.lock.json | 9 + .../OrganizationDomainControllerTests.cs | 268 +++ .../OrganizationsControllerTests.cs | 1 + test/Api.Test/packages.lock.json | 9 + test/Billing.Test/packages.lock.json | 9 + test/Common/packages.lock.json | 9 + .../CreateOrganizationDomainCommandTests.cs | 130 ++ .../DeleteOrganizationDomainCommandTests.cs | 47 + .../GetOrganizationDomainByIdQueryTests.cs | 22 + ...izationDomainByOrganizationIdQueryTests.cs | 22 + .../VerifyOrganizationDomainCommandTests.cs | 135 ++ test/Core.Test/Services/EventServiceTests.cs | 36 +- .../OrganizationDomainServiceTests.cs | 83 + test/Core.Test/packages.lock.json | 9 + test/Icons.Test/packages.lock.json | 9 + .../packages.lock.json | 9 + test/Identity.Test/packages.lock.json | 9 + .../packages.lock.json | 9 + .../packages.lock.json | 9 + test/IntegrationTestCommon/packages.lock.json | 9 + .../2022-11-03_00_OrganizationDomainInit.sql | 285 +++ .../2022-12-08_00_EventsDomainName.sql | 95 + ...1_18_00_FixOrganizationDeleteOrgDomain.sql | 114 ++ util/Migrator/packages.lock.json | 9 + .../2022-11-03_00_OrganizationDomainClaim.sql | 21 + ...anizationDomainClaimRenameNextRunCount.sql | 8 + .../2022-12-08_00_EventsDomainName.sql | 9 + ...9_00_OrganizationDomainLastCheckedDate.sql | 9 + ...202829_OrganizationDomainClaim.Designer.cs | 1722 ++++++++++++++++ .../20221114202829_OrganizationDomainClaim.cs | 49 + ...nDomainClaimRenameNextRunCount.Designer.cs | 1725 ++++++++++++++++ ...ganizationDomainClaimRenameNextRunCount.cs | 24 + ...0221209015017_EventsDomainName.Designer.cs | 1731 ++++++++++++++++ .../20221209015017_EventsDomainName.cs | 25 + ...anizationDomainLastCheckedDate.Designer.cs | 1734 ++++++++++++++++ ...92355_OrganizationDomainLastCheckedDate.cs | 24 + .../DatabaseContextModelSnapshot.cs | 53 + util/MySqlMigrations/packages.lock.json | 9 + ...2022-11-03_00_OrganizationDomainClaim.psql | 21 + ...nizationDomainClaimRenameNextRunCount.psql | 8 + .../2022-12-08_00_EventsDomainName.psql | 9 + ..._00_OrganizationDomainLastCheckedDate.psql | 9 + ...ssAuthRequestAddApprovedColumn.Designer.cs | 108 +- ...192912_OrganizationDomainClaim.Designer.cs | 1733 ++++++++++++++++ .../20221114192912_OrganizationDomainClaim.cs | 46 + ...nDomainClaimRenameNextRunCount.Designer.cs | 1736 ++++++++++++++++ ...ganizationDomainClaimRenameNextRunCount.cs | 24 + ...0221209020447_EventsDomainName.Designer.cs | 1742 ++++++++++++++++ .../20221209020447_EventsDomainName.cs | 24 + ...anizationDomainLastCheckedDate.Designer.cs | 1745 +++++++++++++++++ ...94623_OrganizationDomainLastCheckedDate.cs | 24 + .../DatabaseContextModelSnapshot.cs | 53 + util/PostgresMigrations/packages.lock.json | 9 + util/Setup/packages.lock.json | 9 + util/SqlServerEFScaffold/packages.lock.json | 9 + util/SqliteMigrations/packages.lock.json | 9 + 159 files changed, 17539 insertions(+), 103 deletions(-) create mode 100644 src/Admin/Jobs/DeleteUnverifiedOrganizationDomainsJob.cs create mode 100644 src/Api/Controllers/OrganizationDomainController.cs create mode 100644 src/Api/Jobs/ValidateOrganizationDomainJob.cs create mode 100644 src/Api/Models/Request/OrganizationDomainRequestModel.cs create mode 100644 src/Api/Models/Request/Organizations/OrganizationDomainSsoDetailsRequestModel.cs create mode 100644 src/Api/Models/Response/Organizations/OrganizationDomainResponseModel.cs create mode 100644 src/Api/Models/Response/Organizations/OrganizationDomainSsoDetailsResponseModel.cs create mode 100644 src/Core/Entities/OrganizationDomain.cs create mode 100644 src/Core/Exceptions/DnsQueryException.cs create mode 100644 src/Core/Exceptions/DomainClaimedException.cs create mode 100644 src/Core/Exceptions/DomainVerifiedException.cs create mode 100644 src/Core/Exceptions/DuplicateDomainException.cs create mode 100644 src/Core/MailTemplates/Handlebars/OrganizationDomainUnverified.html.hbs create mode 100644 src/Core/MailTemplates/Handlebars/OrganizationDomainUnverified.text.hbs create mode 100644 src/Core/Models/Data/Organizations/OrganizationDomainSsoDetailsData.cs create mode 100644 src/Core/Models/Mail/OrganizationDomainUnverifiedViewModel.cs create mode 100644 src/Core/OrganizationFeatures/OrganizationDomains/CreateOrganizationDomainCommand.cs create mode 100644 src/Core/OrganizationFeatures/OrganizationDomains/DeleteOrganizationDomainCommand.cs create mode 100644 src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdQuery.cs create mode 100644 src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByOrganizationIdQuery.cs create mode 100644 src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/ICreateOrganizationDomainCommand.cs create mode 100644 src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IDeleteOrganizationDomainCommand.cs create mode 100644 src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByIdQuery.cs create mode 100644 src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByOrganizationIdQuery.cs create mode 100644 src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IVerifyOrganizationDomainCommand.cs create mode 100644 src/Core/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs create mode 100644 src/Core/Repositories/IOrganizationDomainRepository.cs create mode 100644 src/Core/Services/IDnsResolverService.cs create mode 100644 src/Core/Services/IOrganizationDomainService.cs create mode 100644 src/Core/Services/Implementations/DnsResolverService.cs create mode 100644 src/Core/Services/Implementations/OrganizationDomainService.cs create mode 100644 src/Core/Settings/IDomainVerificationSettings.cs create mode 100644 src/Infrastructure.Dapper/Repositories/OrganizationDomainRepository.cs create mode 100644 src/Infrastructure.EntityFramework/Models/OrganizationDomain.cs create mode 100644 src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs create mode 100644 src/Sql/dbo/Stored Procedures/OrganizationDomainSsoDetails_ReadByEmail.sql create mode 100644 src/Sql/dbo/Stored Procedures/OrganizationDomain_Create.sql create mode 100644 src/Sql/dbo/Stored Procedures/OrganizationDomain_DeleteById.sql create mode 100644 src/Sql/dbo/Stored Procedures/OrganizationDomain_DeleteIfExpired.sql create mode 100644 src/Sql/dbo/Stored Procedures/OrganizationDomain_OrganizationDeleted.sql create mode 100644 src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadByClaimedDomain.sql create mode 100644 src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadById.sql create mode 100644 src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadByNextRunDate.sql create mode 100644 src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadByOrganizationId.sql create mode 100644 src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadDomainByOrgIdAndDomainName.sql create mode 100644 src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadIfExpired.sql create mode 100644 src/Sql/dbo/Stored Procedures/OrganizationDomain_Update.sql create mode 100644 src/Sql/dbo/Tables/OrganizationDomain.sql create mode 100644 src/Sql/dbo/Views/OrganizationDomainView.sql create mode 100644 test/Api.Test/Controllers/OrganizationDomainControllerTests.cs create mode 100644 test/Core.Test/OrganizationFeatures/OrganizationDomains/CreateOrganizationDomainCommandTests.cs create mode 100644 test/Core.Test/OrganizationFeatures/OrganizationDomains/DeleteOrganizationDomainCommandTests.cs create mode 100644 test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdQueryTests.cs create mode 100644 test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByOrganizationIdQueryTests.cs create mode 100644 test/Core.Test/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommandTests.cs create mode 100644 test/Core.Test/Services/OrganizationDomainServiceTests.cs create mode 100644 util/Migrator/DbScripts/2022-11-03_00_OrganizationDomainInit.sql create mode 100644 util/Migrator/DbScripts/2022-12-08_00_EventsDomainName.sql create mode 100644 util/Migrator/DbScripts/2023_01_18_00_FixOrganizationDeleteOrgDomain.sql create mode 100644 util/MySqlMigrations/HelperScripts/2022-11-03_00_OrganizationDomainClaim.sql create mode 100644 util/MySqlMigrations/HelperScripts/2022-11-28_00_OrganizationDomainClaimRenameNextRunCount.sql create mode 100644 util/MySqlMigrations/HelperScripts/2022-12-08_00_EventsDomainName.sql create mode 100644 util/MySqlMigrations/HelperScripts/2022-12-09_00_OrganizationDomainLastCheckedDate.sql create mode 100644 util/MySqlMigrations/Migrations/20221114202829_OrganizationDomainClaim.Designer.cs create mode 100644 util/MySqlMigrations/Migrations/20221114202829_OrganizationDomainClaim.cs create mode 100644 util/MySqlMigrations/Migrations/20221129004644_OrganizationDomainClaimRenameNextRunCount.Designer.cs create mode 100644 util/MySqlMigrations/Migrations/20221129004644_OrganizationDomainClaimRenameNextRunCount.cs create mode 100644 util/MySqlMigrations/Migrations/20221209015017_EventsDomainName.Designer.cs create mode 100644 util/MySqlMigrations/Migrations/20221209015017_EventsDomainName.cs create mode 100644 util/MySqlMigrations/Migrations/20221209192355_OrganizationDomainLastCheckedDate.Designer.cs create mode 100644 util/MySqlMigrations/Migrations/20221209192355_OrganizationDomainLastCheckedDate.cs create mode 100644 util/PostgresMigrations/HelperScripts/2022-11-03_00_OrganizationDomainClaim.psql create mode 100644 util/PostgresMigrations/HelperScripts/2022-11-28_00_OrganizationDomainClaimRenameNextRunCount.psql create mode 100644 util/PostgresMigrations/HelperScripts/2022-12-08_00_EventsDomainName.psql create mode 100644 util/PostgresMigrations/HelperScripts/2022-12-09_00_OrganizationDomainLastCheckedDate.psql create mode 100644 util/PostgresMigrations/Migrations/20221114192912_OrganizationDomainClaim.Designer.cs create mode 100644 util/PostgresMigrations/Migrations/20221114192912_OrganizationDomainClaim.cs create mode 100644 util/PostgresMigrations/Migrations/20221129032517_OrganizationDomainClaimRenameNextRunCount.Designer.cs create mode 100644 util/PostgresMigrations/Migrations/20221129032517_OrganizationDomainClaimRenameNextRunCount.cs create mode 100644 util/PostgresMigrations/Migrations/20221209020447_EventsDomainName.Designer.cs create mode 100644 util/PostgresMigrations/Migrations/20221209020447_EventsDomainName.cs create mode 100644 util/PostgresMigrations/Migrations/20221209194623_OrganizationDomainLastCheckedDate.Designer.cs create mode 100644 util/PostgresMigrations/Migrations/20221209194623_OrganizationDomainLastCheckedDate.cs diff --git a/bitwarden_license/src/Commercial.Core/packages.lock.json b/bitwarden_license/src/Commercial.Core/packages.lock.json index a13ec7e69a..3bd4c03afd 100644 --- a/bitwarden_license/src/Commercial.Core/packages.lock.json +++ b/bitwarden_license/src/Commercial.Core/packages.lock.json @@ -126,6 +126,14 @@ "System.Xml.XPath.XmlDocument": "4.3.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2553,6 +2561,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/packages.lock.json b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/packages.lock.json index 3803bc704f..9a1d2310a9 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/packages.lock.json +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/packages.lock.json @@ -144,6 +144,14 @@ "System.Xml.XPath.XmlDocument": "4.3.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2726,6 +2734,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/bitwarden_license/src/Scim/Controllers/v2/UsersController.cs b/bitwarden_license/src/Scim/Controllers/v2/UsersController.cs index ea58a90af3..b198ff79e6 100644 --- a/bitwarden_license/src/Scim/Controllers/v2/UsersController.cs +++ b/bitwarden_license/src/Scim/Controllers/v2/UsersController.cs @@ -97,11 +97,11 @@ public class UsersController : Controller if (model.Active && orgUser.Status == OrganizationUserStatusType.Revoked) { - await _organizationService.RestoreUserAsync(orgUser, null, _userService); + await _organizationService.RestoreUserAsync(orgUser, EventSystemUser.SCIM, _userService); } else if (!model.Active && orgUser.Status != OrganizationUserStatusType.Revoked) { - await _organizationService.RevokeUserAsync(orgUser, null); + await _organizationService.RevokeUserAsync(orgUser, EventSystemUser.SCIM); } // Have to get full details object for response model diff --git a/bitwarden_license/src/Scim/packages.lock.json b/bitwarden_license/src/Scim/packages.lock.json index 3e79e8b6dd..a68dd3d1eb 100644 --- a/bitwarden_license/src/Scim/packages.lock.json +++ b/bitwarden_license/src/Scim/packages.lock.json @@ -157,6 +157,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -3002,6 +3010,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/bitwarden_license/src/Sso/packages.lock.json b/bitwarden_license/src/Sso/packages.lock.json index 7a7952f437..679027d573 100644 --- a/bitwarden_license/src/Sso/packages.lock.json +++ b/bitwarden_license/src/Sso/packages.lock.json @@ -160,6 +160,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2872,6 +2880,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/bitwarden_license/test/Commercial.Core.Test/packages.lock.json b/bitwarden_license/test/Commercial.Core.Test/packages.lock.json index 9302ac09e7..5699c2e8a8 100644 --- a/bitwarden_license/test/Commercial.Core.Test/packages.lock.json +++ b/bitwarden_license/test/Commercial.Core.Test/packages.lock.json @@ -203,6 +203,14 @@ "System.Xml.XmlDocument": "4.3.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fare": { "type": "Transitive", "resolved": "2.1.1", @@ -2822,6 +2830,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/bitwarden_license/test/Scim.IntegrationTest/packages.lock.json b/bitwarden_license/test/Scim.IntegrationTest/packages.lock.json index 74555c6867..00e5f26b92 100644 --- a/bitwarden_license/test/Scim.IntegrationTest/packages.lock.json +++ b/bitwarden_license/test/Scim.IntegrationTest/packages.lock.json @@ -246,6 +246,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fare": { "type": "Transitive", "resolved": "2.1.1", @@ -3406,6 +3414,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/bitwarden_license/test/Scim.Test/packages.lock.json b/bitwarden_license/test/Scim.Test/packages.lock.json index e89d8735ff..8c8143f8f6 100644 --- a/bitwarden_license/test/Scim.Test/packages.lock.json +++ b/bitwarden_license/test/Scim.Test/packages.lock.json @@ -234,6 +234,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fare": { "type": "Transitive", "resolved": "2.1.1", @@ -3251,6 +3259,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/perf/MicroBenchmarks/packages.lock.json b/perf/MicroBenchmarks/packages.lock.json index 49a034c094..d19999b140 100644 --- a/perf/MicroBenchmarks/packages.lock.json +++ b/perf/MicroBenchmarks/packages.lock.json @@ -156,6 +156,14 @@ "resolved": "2.4.3", "contentHash": "U2FC9Y8NyIxxU6MpFFdWWu1xwiqz/61v/Doou7kmVjpeIEMLWyiNNkzNlSE84kyJ0O1LKApuEj5z48Ow0Hi4OQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2660,6 +2668,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/src/Admin/Jobs/DeleteUnverifiedOrganizationDomainsJob.cs b/src/Admin/Jobs/DeleteUnverifiedOrganizationDomainsJob.cs new file mode 100644 index 0000000000..0d2f2d8606 --- /dev/null +++ b/src/Admin/Jobs/DeleteUnverifiedOrganizationDomainsJob.cs @@ -0,0 +1,31 @@ +using Bit.Core; +using Bit.Core.Jobs; +using Bit.Core.Services; +using Quartz; + +namespace Bit.Admin.Jobs; + +public class DeleteUnverifiedOrganizationDomainsJob : BaseJob +{ + private readonly IServiceProvider _serviceProvider; + + public DeleteUnverifiedOrganizationDomainsJob( + IServiceProvider serviceProvider, + ILogger logger) + : base(logger) + { + _serviceProvider = serviceProvider; + } + + protected override async Task ExecuteJobAsync(IJobExecutionContext context) + { + _logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteUnverifiedOrganizationDomainsJob: Start"); + using (var serviceScope = _serviceProvider.CreateScope()) + { + var organizationDomainService = + serviceScope.ServiceProvider.GetRequiredService(); + await organizationDomainService.OrganizationDomainMaintenanceAsync(); + } + _logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: DeleteUnverifiedOrganizationDomainsJob: End"); + } +} diff --git a/src/Admin/Jobs/JobsHostedService.cs b/src/Admin/Jobs/JobsHostedService.cs index fd83e07af8..5aa770befc 100644 --- a/src/Admin/Jobs/JobsHostedService.cs +++ b/src/Admin/Jobs/JobsHostedService.cs @@ -64,6 +64,11 @@ public class JobsHostedService : BaseJobsHostedService .StartNow() .WithCronSchedule("0 */15 * ? * *") .Build(); + var everyDayAtTwoAmUtcTrigger = TriggerBuilder.Create() + .WithIdentity("EveryDayAtTwoAmUtcTrigger") + .StartNow() + .WithCronSchedule("0 0 2 ? * * *") + .Build(); var jobs = new List> { @@ -74,6 +79,7 @@ public class JobsHostedService : BaseJobsHostedService new Tuple(typeof(DeleteCiphersJob), everyDayAtMidnightUtc), new Tuple(typeof(DatabaseExpiredSponsorshipsJob), everyMondayAtMidnightTrigger), new Tuple(typeof(DeleteAuthRequestsJob), everyFifteenMinutesTrigger), + new Tuple(typeof(DeleteUnverifiedOrganizationDomainsJob), everyDayAtTwoAmUtcTrigger), }; if (!_globalSettings.SelfHosted) @@ -98,5 +104,6 @@ public class JobsHostedService : BaseJobsHostedService services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } } diff --git a/src/Admin/packages.lock.json b/src/Admin/packages.lock.json index 3c4729d2a4..cf58d395fe 100644 --- a/src/Admin/packages.lock.json +++ b/src/Admin/packages.lock.json @@ -199,6 +199,14 @@ "dbup-core": "4.5.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -3314,6 +3322,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/src/Api/Controllers/OrganizationDomainController.cs b/src/Api/Controllers/OrganizationDomainController.cs new file mode 100644 index 0000000000..23e4f51bc7 --- /dev/null +++ b/src/Api/Controllers/OrganizationDomainController.cs @@ -0,0 +1,143 @@ +using Bit.Api.Models.Request; +using Bit.Api.Models.Request.Organizations; +using Bit.Api.Models.Response; +using Bit.Api.Models.Response.Organizations; +using Bit.Core.Context; +using Bit.Core.Entities; +using Bit.Core.Exceptions; +using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; +using Bit.Core.Repositories; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.Api.Controllers; + +[Route("organizations")] +[Authorize("Application")] +public class OrganizationDomainController : Controller +{ + private readonly ICreateOrganizationDomainCommand _createOrganizationDomainCommand; + private readonly IVerifyOrganizationDomainCommand _verifyOrganizationDomainCommand; + private readonly IDeleteOrganizationDomainCommand _deleteOrganizationDomainCommand; + private readonly IGetOrganizationDomainByIdQuery _getOrganizationDomainByIdQuery; + private readonly IGetOrganizationDomainByOrganizationIdQuery _getOrganizationDomainByOrganizationIdQuery; + private readonly ICurrentContext _currentContext; + private readonly IOrganizationRepository _organizationRepository; + private readonly IOrganizationDomainRepository _organizationDomainRepository; + + public OrganizationDomainController( + ICreateOrganizationDomainCommand createOrganizationDomainCommand, + IVerifyOrganizationDomainCommand verifyOrganizationDomainCommand, + IDeleteOrganizationDomainCommand deleteOrganizationDomainCommand, + IGetOrganizationDomainByIdQuery getOrganizationDomainByIdQuery, + IGetOrganizationDomainByOrganizationIdQuery getOrganizationDomainByOrganizationIdQuery, + ICurrentContext currentContext, + IOrganizationRepository organizationRepository, + IOrganizationDomainRepository organizationDomainRepository) + { + _createOrganizationDomainCommand = createOrganizationDomainCommand; + _verifyOrganizationDomainCommand = verifyOrganizationDomainCommand; + _deleteOrganizationDomainCommand = deleteOrganizationDomainCommand; + _getOrganizationDomainByIdQuery = getOrganizationDomainByIdQuery; + _getOrganizationDomainByOrganizationIdQuery = getOrganizationDomainByOrganizationIdQuery; + _currentContext = currentContext; + _organizationRepository = organizationRepository; + _organizationDomainRepository = organizationDomainRepository; + } + + [HttpGet("{orgId}/domain")] + public async Task> Get(string orgId) + { + var orgIdGuid = new Guid(orgId); + await ValidateOrganizationAccessAsync(orgIdGuid); + + var domains = await _getOrganizationDomainByOrganizationIdQuery + .GetDomainsByOrganizationId(orgIdGuid); + var response = domains.Select(x => new OrganizationDomainResponseModel(x)).ToList(); + return new ListResponseModel(response); + } + + [HttpGet("{orgId}/domain/{id}")] + public async Task Get(string orgId, string id) + { + var orgIdGuid = new Guid(orgId); + var IdGuid = new Guid(id); + await ValidateOrganizationAccessAsync(orgIdGuid); + + var domain = await _getOrganizationDomainByIdQuery.GetOrganizationDomainById(IdGuid); + if (domain is null) + { + throw new NotFoundException(); + } + + return new OrganizationDomainResponseModel(domain); + } + + [HttpPost("{orgId}/domain")] + public async Task Post(string orgId, + [FromBody] OrganizationDomainRequestModel model) + { + var orgIdGuid = new Guid(orgId); + await ValidateOrganizationAccessAsync(orgIdGuid); + + var organizationDomain = new OrganizationDomain + { + OrganizationId = orgIdGuid, + Txt = model.Txt, + DomainName = model.DomainName.ToLower() + }; + + var domain = await _createOrganizationDomainCommand.CreateAsync(organizationDomain); + return new OrganizationDomainResponseModel(domain); + } + + [HttpPost("{orgId}/domain/{id}/verify")] + public async Task Verify(string orgId, string id) + { + var orgIdGuid = new Guid(orgId); + var idGuid = new Guid(id); + await ValidateOrganizationAccessAsync(orgIdGuid); + + var domain = await _verifyOrganizationDomainCommand.VerifyOrganizationDomain(idGuid); + return new OrganizationDomainResponseModel(domain); + } + + [HttpDelete("{orgId}/domain/{id}")] + [HttpPost("{orgId}/domain/{id}/remove")] + public async Task RemoveDomain(string orgId, string id) + { + var orgIdGuid = new Guid(orgId); + var idGuid = new Guid(id); + await ValidateOrganizationAccessAsync(orgIdGuid); + + await _deleteOrganizationDomainCommand.DeleteAsync(idGuid); + } + + [AllowAnonymous] + [HttpPost("domain/sso/details")] // must be post to accept email cleanly + public async Task GetOrgDomainSsoDetails( + [FromBody] OrganizationDomainSsoDetailsRequestModel model) + { + var ssoResult = await _organizationDomainRepository.GetOrganizationDomainSsoDetailsAsync(model.Email); + if (ssoResult is null) + { + throw new NotFoundException("Claimed org domain not found"); + } + + return new OrganizationDomainSsoDetailsResponseModel(ssoResult); + } + + private async Task ValidateOrganizationAccessAsync(Guid orgIdGuid) + { + if (!await _currentContext.ManageSso(orgIdGuid)) + { + throw new UnauthorizedAccessException(); + } + + var organization = await _organizationRepository.GetByIdAsync(orgIdGuid); + if (organization == null) + { + throw new NotFoundException(); + } + } +} diff --git a/src/Api/Controllers/OrganizationsController.cs b/src/Api/Controllers/OrganizationsController.cs index baf0dbfaaf..f616d65292 100644 --- a/src/Api/Controllers/OrganizationsController.cs +++ b/src/Api/Controllers/OrganizationsController.cs @@ -135,6 +135,7 @@ public class OrganizationsController : Controller { throw new NotFoundException(); } + return new OrganizationSubscriptionResponseModel(organization, subscriptionInfo); } else @@ -255,7 +256,7 @@ public class OrganizationsController : Controller } var updateBilling = !_globalSettings.SelfHosted && (model.BusinessName != organization.BusinessName || - model.BillingEmail != organization.BillingEmail); + model.BillingEmail != organization.BillingEmail); var hasRequiredPermissions = updateBilling ? await _currentContext.ManageBilling(orgIdGuid) @@ -304,11 +305,7 @@ public class OrganizationsController : Controller } var result = await _organizationService.UpgradePlanAsync(orgIdGuid, model.ToOrganizationUpgrade()); - return new PaymentResponseModel - { - Success = result.Item1, - PaymentIntentClientSecret = result.Item2 - }; + return new PaymentResponseModel { Success = result.Item1, PaymentIntentClientSecret = result.Item2 }; } [HttpPost("{id}/subscription")] @@ -335,11 +332,7 @@ public class OrganizationsController : Controller } var result = await _organizationService.AdjustSeatsAsync(orgIdGuid, model.SeatAdjustment.Value); - return new PaymentResponseModel - { - Success = true, - PaymentIntentClientSecret = result - }; + return new PaymentResponseModel { Success = true, PaymentIntentClientSecret = result }; } [HttpPost("{id}/storage")] @@ -353,11 +346,7 @@ public class OrganizationsController : Controller } var result = await _organizationService.AdjustStorageAsync(orgIdGuid, model.StorageGbAdjustment.Value); - return new PaymentResponseModel - { - Success = true, - PaymentIntentClientSecret = result - }; + return new PaymentResponseModel { Success = true, PaymentIntentClientSecret = result }; } [HttpPost("{id}/verify-bank")] @@ -548,7 +537,8 @@ public class OrganizationsController : Controller } [HttpGet("{id}/api-key-information/{type?}")] - public async Task> ApiKeyInformation(Guid id, [FromRoute] OrganizationApiKeyType? type) + public async Task> ApiKeyInformation(Guid id, + [FromRoute] OrganizationApiKeyType? type) { if (!await HasApiKeyAccessAsync(id, type)) { @@ -577,8 +567,8 @@ public class OrganizationsController : Controller } var organizationApiKey = await _getOrganizationApiKeyQuery - .GetOrganizationApiKeyAsync(organization.Id, model.Type) ?? - await _createOrganizationApiKeyCommand.CreateAsync(organization.Id, model.Type); + .GetOrganizationApiKeyAsync(organization.Id, model.Type) ?? + await _createOrganizationApiKeyCommand.CreateAsync(organization.Id, model.Type); var user = await _userService.GetUserByPrincipalAsync(User); if (user == null) @@ -679,7 +669,8 @@ public class OrganizationsController : Controller throw new UnauthorizedAccessException(); } - var org = await _organizationService.UpdateOrganizationKeysAsync(new Guid(id), model.PublicKey, model.EncryptedPrivateKey); + var org = await _organizationService.UpdateOrganizationKeysAsync(new Guid(id), model.PublicKey, + model.EncryptedPrivateKey); return new OrganizationKeysResponseModel(org); } diff --git a/src/Api/Jobs/JobsHostedService.cs b/src/Api/Jobs/JobsHostedService.cs index 241a012428..eb35556ab2 100644 --- a/src/Api/Jobs/JobsHostedService.cs +++ b/src/Api/Jobs/JobsHostedService.cs @@ -47,6 +47,12 @@ public class JobsHostedService : BaseJobsHostedService .WithIntervalInHours(24) .RepeatForever()) .Build(); + var validateOrganizationDomainTrigger = TriggerBuilder.Create() + .WithIdentity("ValidateOrganizationDomainTrigger") + .StartNow() + .WithCronSchedule("0 0 * * * ?") + .Build(); + var jobs = new List> { @@ -54,7 +60,8 @@ public class JobsHostedService : BaseJobsHostedService new Tuple(typeof(EmergencyAccessNotificationJob), emergencyAccessNotificationTrigger), new Tuple(typeof(EmergencyAccessTimeoutJob), emergencyAccessTimeoutTrigger), new Tuple(typeof(ValidateUsersJob), everyTopOfTheSixthHourTrigger), - new Tuple(typeof(ValidateOrganizationsJob), everyTwelfthHourAndThirtyMinutesTrigger) + new Tuple(typeof(ValidateOrganizationsJob), everyTwelfthHourAndThirtyMinutesTrigger), + new Tuple(typeof(ValidateOrganizationDomainJob), validateOrganizationDomainTrigger), }; if (_globalSettings.SelfHosted && _globalSettings.EnableCloudCommunication) @@ -78,5 +85,6 @@ public class JobsHostedService : BaseJobsHostedService services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } } diff --git a/src/Api/Jobs/ValidateOrganizationDomainJob.cs b/src/Api/Jobs/ValidateOrganizationDomainJob.cs new file mode 100644 index 0000000000..3d0f2d76c1 --- /dev/null +++ b/src/Api/Jobs/ValidateOrganizationDomainJob.cs @@ -0,0 +1,30 @@ +using Bit.Core; +using Bit.Core.Jobs; +using Bit.Core.Services; +using Quartz; + +namespace Bit.Api.Jobs; + +public class ValidateOrganizationDomainJob : BaseJob +{ + private readonly IServiceProvider _serviceProvider; + public ValidateOrganizationDomainJob( + IServiceProvider serviceProvider, + ILogger logger) + : base(logger) + { + _serviceProvider = serviceProvider; + } + + protected override async Task ExecuteJobAsync(IJobExecutionContext context) + { + _logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: ValidateOrganizationDomainJob: Start"); + using (var serviceScope = _serviceProvider.CreateScope()) + { + var organizationDomainService = + serviceScope.ServiceProvider.GetRequiredService(); + await organizationDomainService.ValidateOrganizationsDomainAsync(); + } + _logger.LogInformation(Constants.BypassFiltersEventId, "Execute job task: ValidateOrganizationDomainJob: End"); + } +} diff --git a/src/Api/Models/Request/OrganizationDomainRequestModel.cs b/src/Api/Models/Request/OrganizationDomainRequestModel.cs new file mode 100644 index 0000000000..28786bf586 --- /dev/null +++ b/src/Api/Models/Request/OrganizationDomainRequestModel.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace Bit.Api.Models.Request; + +public class OrganizationDomainRequestModel +{ + [Required] + public string Txt { get; set; } + + [Required] + public string DomainName { get; set; } +} diff --git a/src/Api/Models/Request/Organizations/OrganizationDomainSsoDetailsRequestModel.cs b/src/Api/Models/Request/Organizations/OrganizationDomainSsoDetailsRequestModel.cs new file mode 100644 index 0000000000..9f9db5b34a --- /dev/null +++ b/src/Api/Models/Request/Organizations/OrganizationDomainSsoDetailsRequestModel.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace Bit.Api.Models.Request.Organizations; + +public class OrganizationDomainSsoDetailsRequestModel +{ + [Required] + [EmailAddress] + public string Email { get; set; } +} diff --git a/src/Api/Models/Response/EventResponseModel.cs b/src/Api/Models/Response/EventResponseModel.cs index e9ef73a57f..bd94dd171f 100644 --- a/src/Api/Models/Response/EventResponseModel.cs +++ b/src/Api/Models/Response/EventResponseModel.cs @@ -31,6 +31,7 @@ public class EventResponseModel : ResponseModel IpAddress = ev.IpAddress; InstallationId = ev.InstallationId; SystemUser = ev.SystemUser; + DomainName = ev.DomainName; } public EventType Type { get; set; } @@ -50,4 +51,5 @@ public class EventResponseModel : ResponseModel public DeviceType? DeviceType { get; set; } public string IpAddress { get; set; } public EventSystemUser? SystemUser { get; set; } + public string DomainName { get; set; } } diff --git a/src/Api/Models/Response/Organizations/OrganizationDomainResponseModel.cs b/src/Api/Models/Response/Organizations/OrganizationDomainResponseModel.cs new file mode 100644 index 0000000000..b05a732ee7 --- /dev/null +++ b/src/Api/Models/Response/Organizations/OrganizationDomainResponseModel.cs @@ -0,0 +1,36 @@ +using Bit.Core.Entities; +using Bit.Core.Models.Api; + +namespace Bit.Api.Models.Response.Organizations; + +public class OrganizationDomainResponseModel : ResponseModel +{ + public OrganizationDomainResponseModel(OrganizationDomain organizationDomain, string obj = "organizationDomain") + : base(obj) + { + if (organizationDomain == null) + { + throw new ArgumentNullException(nameof(organizationDomain)); + } + + Id = organizationDomain.Id.ToString(); + OrganizationId = organizationDomain.OrganizationId.ToString(); + Txt = organizationDomain.Txt; + DomainName = organizationDomain.DomainName; + CreationDate = organizationDomain.CreationDate; + NextRunDate = organizationDomain.NextRunDate; + JobRunCount = organizationDomain.JobRunCount; + VerifiedDate = organizationDomain.VerifiedDate; + LastCheckedDate = organizationDomain.LastCheckedDate; + } + + public string Id { get; set; } + public string OrganizationId { get; set; } + public string Txt { get; set; } + public string DomainName { get; set; } + public DateTime CreationDate { get; set; } + public DateTime NextRunDate { get; set; } + public int JobRunCount { get; set; } + public DateTime? VerifiedDate { get; set; } + public DateTime? LastCheckedDate { get; set; } +} diff --git a/src/Api/Models/Response/Organizations/OrganizationDomainSsoDetailsResponseModel.cs b/src/Api/Models/Response/Organizations/OrganizationDomainSsoDetailsResponseModel.cs new file mode 100644 index 0000000000..44c4acea90 --- /dev/null +++ b/src/Api/Models/Response/Organizations/OrganizationDomainSsoDetailsResponseModel.cs @@ -0,0 +1,28 @@ +using Bit.Core.Models.Api; +using Bit.Core.Models.Data.Organizations; + +namespace Bit.Api.Models.Response.Organizations; + +public class OrganizationDomainSsoDetailsResponseModel : ResponseModel +{ + public OrganizationDomainSsoDetailsResponseModel(OrganizationDomainSsoDetailsData data, string obj = "organizationDomainSsoDetails") + : base(obj) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + SsoAvailable = data.SsoAvailable; + DomainName = data.DomainName; + OrganizationIdentifier = data.OrganizationIdentifier; + SsoRequired = data.SsoRequired; + VerifiedDate = data.VerifiedDate; + } + + public bool SsoAvailable { get; private set; } + public string DomainName { get; private set; } + public string OrganizationIdentifier { get; private set; } + public bool SsoRequired { get; private set; } + public DateTime? VerifiedDate { get; private set; } +} diff --git a/src/Api/Utilities/ExceptionHandlerFilterAttribute.cs b/src/Api/Utilities/ExceptionHandlerFilterAttribute.cs index 60506e46d0..4dadedeb3e 100644 --- a/src/Api/Utilities/ExceptionHandlerFilterAttribute.cs +++ b/src/Api/Utilities/ExceptionHandlerFilterAttribute.cs @@ -92,6 +92,11 @@ public class ExceptionHandlerFilterAttribute : ExceptionFilterAttribute errorMessage = "Unauthorized."; context.HttpContext.Response.StatusCode = 401; } + else if (exception is ConflictException) + { + errorMessage = exception.Message; + context.HttpContext.Response.StatusCode = 409; + } else if (exception is AggregateException aggregateException) { context.HttpContext.Response.StatusCode = 400; diff --git a/src/Api/packages.lock.json b/src/Api/packages.lock.json index 4df2bb85a3..e540064cd3 100644 --- a/src/Api/packages.lock.json +++ b/src/Api/packages.lock.json @@ -171,6 +171,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2798,6 +2806,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/src/Billing/packages.lock.json b/src/Billing/packages.lock.json index 96fdbe7b72..dc46ffb62a 100644 --- a/src/Billing/packages.lock.json +++ b/src/Billing/packages.lock.json @@ -160,6 +160,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -3249,6 +3257,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 0f4768125d..1ddc93a95e 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -27,6 +27,7 @@ + diff --git a/src/Core/Entities/Event.cs b/src/Core/Entities/Event.cs index 6800b1120e..5fa484a0fe 100644 --- a/src/Core/Entities/Event.cs +++ b/src/Core/Entities/Event.cs @@ -28,6 +28,7 @@ public class Event : ITableObject, IEvent IpAddress = e.IpAddress; ActingUserId = e.ActingUserId; SystemUser = e.SystemUser; + DomainName = e.DomainName; } public Guid Id { get; set; } @@ -49,6 +50,8 @@ public class Event : ITableObject, IEvent public string IpAddress { get; set; } public Guid? ActingUserId { get; set; } public EventSystemUser? SystemUser { get; set; } + public string DomainName { get; set; } + public void SetNewId() { diff --git a/src/Core/Entities/OrganizationDomain.cs b/src/Core/Entities/OrganizationDomain.cs new file mode 100644 index 0000000000..d275d075b2 --- /dev/null +++ b/src/Core/Entities/OrganizationDomain.cs @@ -0,0 +1,48 @@ +using System.ComponentModel.DataAnnotations; +using Bit.Core.Utilities; + +namespace Bit.Core.Entities; + +public class OrganizationDomain : ITableObject +{ + public Guid Id { get; set; } + public Guid OrganizationId { get; set; } + public string Txt { get; set; } + [MaxLength(255)] + public string DomainName { get; set; } + public DateTime CreationDate { get; set; } = DateTime.UtcNow; + public DateTime? VerifiedDate { get; private set; } + public DateTime NextRunDate { get; private set; } + public DateTime? LastCheckedDate { get; private set; } + public int JobRunCount { get; private set; } + public void SetNewId() => Id = CoreHelpers.GenerateComb(); + + public void SetNextRunDate(int interval) + { + //verification can take up to 72 hours + //1st job runs after 12hrs, 2nd after 24hrs and 3rd after 36hrs + NextRunDate = JobRunCount == 0 + ? CreationDate.AddHours(interval) + : NextRunDate.AddHours((JobRunCount + 1) * interval); + } + + public void SetJobRunCount() + { + if (JobRunCount == 3) + { + return; + } + + JobRunCount++; + } + + public void SetVerifiedDate() + { + VerifiedDate = DateTime.UtcNow; + } + + public void SetLastCheckedDate() + { + LastCheckedDate = DateTime.UtcNow; + } +} diff --git a/src/Core/Enums/DeviceType.cs b/src/Core/Enums/DeviceType.cs index 596a43b038..9534cfd71a 100644 --- a/src/Core/Enums/DeviceType.cs +++ b/src/Core/Enums/DeviceType.cs @@ -48,4 +48,6 @@ public enum DeviceType : byte SafariExtension = 20, [Display(Name = "SDK")] SDK = 21, + [Display(Name = "Server")] + Server = 22 } diff --git a/src/Core/Enums/EventSystemUser.cs b/src/Core/Enums/EventSystemUser.cs index 0f19d126c8..09c4e68f41 100644 --- a/src/Core/Enums/EventSystemUser.cs +++ b/src/Core/Enums/EventSystemUser.cs @@ -2,5 +2,6 @@ public enum EventSystemUser : byte { - SCIM = 1 + SCIM = 1, + DomainVerification = 2 } diff --git a/src/Core/Enums/EventType.cs b/src/Core/Enums/EventType.cs index 09a1afffd9..da3b64fdb5 100644 --- a/src/Core/Enums/EventType.cs +++ b/src/Core/Enums/EventType.cs @@ -75,4 +75,9 @@ public enum EventType : int ProviderOrganization_Added = 1901, ProviderOrganization_Removed = 1902, ProviderOrganization_VaultAccessed = 1903, + + OrganizationDomain_Added = 1904, + OrganizationDomain_Removed = 1905, + OrganizationDomain_Verified = 1906, + OrganizationDomain_NotVerified = 1907 } diff --git a/src/Core/Exceptions/ConflictException.cs b/src/Core/Exceptions/ConflictException.cs index 8a3186ac81..27b90a657f 100644 --- a/src/Core/Exceptions/ConflictException.cs +++ b/src/Core/Exceptions/ConflictException.cs @@ -1,3 +1,7 @@ namespace Bit.Core.Exceptions; -public class ConflictException : Exception { } +public class ConflictException : Exception +{ + public ConflictException() : base("Conflict.") { } + public ConflictException(string message) : base(message) { } +} diff --git a/src/Core/Exceptions/DnsQueryException.cs b/src/Core/Exceptions/DnsQueryException.cs new file mode 100644 index 0000000000..57b2c56daa --- /dev/null +++ b/src/Core/Exceptions/DnsQueryException.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Exceptions; + +public class DnsQueryException : Exception +{ + public DnsQueryException(string message) + : base(message) { } +} diff --git a/src/Core/Exceptions/DomainClaimedException.cs b/src/Core/Exceptions/DomainClaimedException.cs new file mode 100644 index 0000000000..09ccb3d0d8 --- /dev/null +++ b/src/Core/Exceptions/DomainClaimedException.cs @@ -0,0 +1,10 @@ +namespace Bit.Core.Exceptions; + +public class DomainClaimedException : Exception +{ + public DomainClaimedException() + : base("The domain is not available to be claimed.") + { + + } +} diff --git a/src/Core/Exceptions/DomainVerifiedException.cs b/src/Core/Exceptions/DomainVerifiedException.cs new file mode 100644 index 0000000000..d3a3fd4de4 --- /dev/null +++ b/src/Core/Exceptions/DomainVerifiedException.cs @@ -0,0 +1,10 @@ +namespace Bit.Core.Exceptions; + +public class DomainVerifiedException : Exception +{ + public DomainVerifiedException() + : base("Domain has already been verified.") + { + + } +} diff --git a/src/Core/Exceptions/DuplicateDomainException.cs b/src/Core/Exceptions/DuplicateDomainException.cs new file mode 100644 index 0000000000..8d347dda55 --- /dev/null +++ b/src/Core/Exceptions/DuplicateDomainException.cs @@ -0,0 +1,10 @@ +namespace Bit.Core.Exceptions; + +public class DuplicateDomainException : Exception +{ + public DuplicateDomainException() + : base("A domain already exists for this organization.") + { + + } +} diff --git a/src/Core/MailTemplates/Handlebars/OrganizationDomainUnverified.html.hbs b/src/Core/MailTemplates/Handlebars/OrganizationDomainUnverified.html.hbs new file mode 100644 index 0000000000..11b482acda --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/OrganizationDomainUnverified.html.hbs @@ -0,0 +1,27 @@ +{{#>FullHtmlLayout}} + + + + + + + + + + + + + +
+ The domain {{DomainName}} in your Bitwarden organization could not be verified. +
+ Check the corresponding record in your domain host. Then reverify this domain in Bitwarden to use it for your organization. +
+ The domain will be removed from your organization in 7 days if it is not verified. +
+ + Manage Domains + +
+
+{{/FullHtmlLayout}} \ No newline at end of file diff --git a/src/Core/MailTemplates/Handlebars/OrganizationDomainUnverified.text.hbs b/src/Core/MailTemplates/Handlebars/OrganizationDomainUnverified.text.hbs new file mode 100644 index 0000000000..f056bf26c3 --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/OrganizationDomainUnverified.text.hbs @@ -0,0 +1,10 @@ +{{#>BasicTextLayout}} +The domain {{DomainName}} in your Bitwarden organization could not be verified. + +Check the corresponding record in your domain host. Then reverify this domain in Bitwarden to use it for your organization. + +The domain will be removed from your organization in 7 days if it is not verified. + +{{Url}} + +{{/BasicTextLayout}} \ No newline at end of file diff --git a/src/Core/Models/Data/EventMessage.cs b/src/Core/Models/Data/EventMessage.cs index d140a86cdb..c969df53d2 100644 --- a/src/Core/Models/Data/EventMessage.cs +++ b/src/Core/Models/Data/EventMessage.cs @@ -32,4 +32,5 @@ public class EventMessage : IEvent public string IpAddress { get; set; } public Guid? IdempotencyId { get; private set; } = Guid.NewGuid(); public EventSystemUser? SystemUser { get; set; } + public string DomainName { get; set; } } diff --git a/src/Core/Models/Data/EventTableEntity.cs b/src/Core/Models/Data/EventTableEntity.cs index 0668fdc1d8..9e49d3f686 100644 --- a/src/Core/Models/Data/EventTableEntity.cs +++ b/src/Core/Models/Data/EventTableEntity.cs @@ -27,6 +27,7 @@ public class EventTableEntity : TableEntity, IEvent IpAddress = e.IpAddress; ActingUserId = e.ActingUserId; SystemUser = e.SystemUser; + DomainName = e.DomainName; } public DateTime Date { get; set; } @@ -46,6 +47,7 @@ public class EventTableEntity : TableEntity, IEvent public string IpAddress { get; set; } public Guid? ActingUserId { get; set; } public EventSystemUser? SystemUser { get; set; } + public string DomainName { get; set; } public override IDictionary WriteEntity(OperationContext operationContext) { diff --git a/src/Core/Models/Data/IEvent.cs b/src/Core/Models/Data/IEvent.cs index 290b9e66e4..5c2e4b8364 100644 --- a/src/Core/Models/Data/IEvent.cs +++ b/src/Core/Models/Data/IEvent.cs @@ -21,4 +21,5 @@ public interface IEvent string IpAddress { get; set; } DateTime Date { get; set; } EventSystemUser? SystemUser { get; set; } + string DomainName { get; set; } } diff --git a/src/Core/Models/Data/Organizations/OrganizationDomainSsoDetailsData.cs b/src/Core/Models/Data/Organizations/OrganizationDomainSsoDetailsData.cs new file mode 100644 index 0000000000..66b6081f1d --- /dev/null +++ b/src/Core/Models/Data/Organizations/OrganizationDomainSsoDetailsData.cs @@ -0,0 +1,16 @@ +using Bit.Core.Enums; + +namespace Bit.Core.Models.Data.Organizations; + +public class OrganizationDomainSsoDetailsData +{ + public Guid OrganizationId { get; set; } + public string OrganizationName { get; set; } + public string DomainName { get; set; } + public bool SsoAvailable { get; set; } + public string OrganizationIdentifier { get; set; } + public bool SsoRequired { get; set; } + public PolicyType PolicyType { get; set; } + public DateTime? VerifiedDate { get; set; } + public bool OrganizationEnabled { get; set; } +} diff --git a/src/Core/Models/Data/Organizations/OrganizationUsers/OrganizationUserUserDetails.cs b/src/Core/Models/Data/Organizations/OrganizationUsers/OrganizationUserUserDetails.cs index 74e06182bf..0576904cd0 100644 --- a/src/Core/Models/Data/Organizations/OrganizationUsers/OrganizationUserUserDetails.cs +++ b/src/Core/Models/Data/Organizations/OrganizationUsers/OrganizationUserUserDetails.cs @@ -68,4 +68,10 @@ public class OrganizationUserUserDetails : IExternal, ITwoFactorProvidersUser return Status != OrganizationUserStatusType.Revoked; } } + + public Permissions GetPermissions() + { + return string.IsNullOrWhiteSpace(Permissions) ? null + : CoreHelpers.LoadClassFromJsonData(Permissions); + } } diff --git a/src/Core/Models/Mail/OrganizationDomainUnverifiedViewModel.cs b/src/Core/Models/Mail/OrganizationDomainUnverifiedViewModel.cs new file mode 100644 index 0000000000..a0547ed3a1 --- /dev/null +++ b/src/Core/Models/Mail/OrganizationDomainUnverifiedViewModel.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Models.Mail; + +public class OrganizationDomainUnverifiedViewModel +{ + public string Url { get; set; } + public string DomainName { get; set; } +} diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/CreateOrganizationDomainCommand.cs b/src/Core/OrganizationFeatures/OrganizationDomains/CreateOrganizationDomainCommand.cs new file mode 100644 index 0000000000..e5aa4a09ad --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationDomains/CreateOrganizationDomainCommand.cs @@ -0,0 +1,76 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Settings; +using Microsoft.Extensions.Logging; + +namespace Bit.Core.OrganizationFeatures.OrganizationDomains; + +public class CreateOrganizationDomainCommand : ICreateOrganizationDomainCommand +{ + private readonly IOrganizationDomainRepository _organizationDomainRepository; + private readonly IEventService _eventService; + private readonly IDnsResolverService _dnsResolverService; + private readonly ILogger _logger; + private readonly IGlobalSettings _globalSettings; + + public CreateOrganizationDomainCommand( + IOrganizationDomainRepository organizationDomainRepository, + IEventService eventService, + IDnsResolverService dnsResolverService, + ILogger logger, + IGlobalSettings globalSettings) + { + _organizationDomainRepository = organizationDomainRepository; + _eventService = eventService; + _dnsResolverService = dnsResolverService; + _logger = logger; + _globalSettings = globalSettings; + } + + public async Task CreateAsync(OrganizationDomain organizationDomain) + { + //Domains claimed and verified by an organization cannot be claimed + var claimedDomain = + await _organizationDomainRepository.GetClaimedDomainsByDomainNameAsync(organizationDomain.DomainName); + if (claimedDomain.Any()) + { + throw new ConflictException("The domain is not available to be claimed."); + } + + //check for duplicate domain entry for an organization + var duplicateOrgDomain = + await _organizationDomainRepository.GetDomainByOrgIdAndDomainNameAsync(organizationDomain.OrganizationId, + organizationDomain.DomainName); + if (duplicateOrgDomain is not null) + { + throw new ConflictException("A domain already exists for this organization."); + } + + try + { + if (await _dnsResolverService.ResolveAsync(organizationDomain.DomainName, organizationDomain.Txt)) + { + organizationDomain.SetVerifiedDate(); + } + } + catch (Exception e) + { + _logger.LogError("Error verifying Organization domain.", e); + } + + organizationDomain.SetNextRunDate(_globalSettings.DomainVerification.VerificationInterval); + organizationDomain.SetLastCheckedDate(); + + var orgDomain = await _organizationDomainRepository.CreateAsync(organizationDomain); + + await _eventService.LogOrganizationDomainEventAsync(orgDomain, EventType.OrganizationDomain_Added); + await _eventService.LogOrganizationDomainEventAsync(orgDomain, + orgDomain.VerifiedDate != null ? EventType.OrganizationDomain_Verified : EventType.OrganizationDomain_NotVerified); + + return orgDomain; + } +} diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/DeleteOrganizationDomainCommand.cs b/src/Core/OrganizationFeatures/OrganizationDomains/DeleteOrganizationDomainCommand.cs new file mode 100644 index 0000000000..c420609130 --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationDomains/DeleteOrganizationDomainCommand.cs @@ -0,0 +1,32 @@ +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; +using Bit.Core.Repositories; +using Bit.Core.Services; + +namespace Bit.Core.OrganizationFeatures.OrganizationDomains; + +public class DeleteOrganizationDomainCommand : IDeleteOrganizationDomainCommand +{ + private readonly IOrganizationDomainRepository _organizationDomainRepository; + private readonly IEventService _eventService; + + public DeleteOrganizationDomainCommand(IOrganizationDomainRepository organizationDomainRepository, + IEventService eventService) + { + _organizationDomainRepository = organizationDomainRepository; + _eventService = eventService; + } + + public async Task DeleteAsync(Guid id) + { + var domain = await _organizationDomainRepository.GetByIdAsync(id); + if (domain is null) + { + throw new NotFoundException(); + } + + await _organizationDomainRepository.DeleteAsync(domain); + await _eventService.LogOrganizationDomainEventAsync(domain, EventType.OrganizationDomain_Removed); + } +} diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdQuery.cs b/src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdQuery.cs new file mode 100644 index 0000000000..8037fa8ec2 --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdQuery.cs @@ -0,0 +1,18 @@ +using Bit.Core.Entities; +using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; +using Bit.Core.Repositories; + +namespace Bit.Core.OrganizationFeatures.OrganizationDomains; + +public class GetOrganizationDomainByIdQuery : IGetOrganizationDomainByIdQuery +{ + private readonly IOrganizationDomainRepository _organizationDomainRepository; + + public GetOrganizationDomainByIdQuery(IOrganizationDomainRepository organizationDomainRepository) + { + _organizationDomainRepository = organizationDomainRepository; + } + + public async Task GetOrganizationDomainById(Guid id) + => await _organizationDomainRepository.GetByIdAsync(id); +} diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByOrganizationIdQuery.cs b/src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByOrganizationIdQuery.cs new file mode 100644 index 0000000000..6b94dbf173 --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByOrganizationIdQuery.cs @@ -0,0 +1,18 @@ +using Bit.Core.Entities; +using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; +using Bit.Core.Repositories; + +namespace Bit.Core.OrganizationFeatures.OrganizationDomains; + +public class GetOrganizationDomainByOrganizationIdQuery : IGetOrganizationDomainByOrganizationIdQuery +{ + private readonly IOrganizationDomainRepository _organizationDomainRepository; + + public GetOrganizationDomainByOrganizationIdQuery(IOrganizationDomainRepository organizationDomainRepository) + { + _organizationDomainRepository = organizationDomainRepository; + } + + public async Task> GetDomainsByOrganizationId(Guid orgId) + => await _organizationDomainRepository.GetDomainsByOrganizationIdAsync(orgId); +} diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/ICreateOrganizationDomainCommand.cs b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/ICreateOrganizationDomainCommand.cs new file mode 100644 index 0000000000..79df45396c --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/ICreateOrganizationDomainCommand.cs @@ -0,0 +1,8 @@ +using Bit.Core.Entities; + +namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; + +public interface ICreateOrganizationDomainCommand +{ + Task CreateAsync(OrganizationDomain organizationDomain); +} diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IDeleteOrganizationDomainCommand.cs b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IDeleteOrganizationDomainCommand.cs new file mode 100644 index 0000000000..4a5cc1c556 --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IDeleteOrganizationDomainCommand.cs @@ -0,0 +1,6 @@ +namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; + +public interface IDeleteOrganizationDomainCommand +{ + Task DeleteAsync(Guid id); +} diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByIdQuery.cs b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByIdQuery.cs new file mode 100644 index 0000000000..765007f42f --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByIdQuery.cs @@ -0,0 +1,8 @@ +using Bit.Core.Entities; + +namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; + +public interface IGetOrganizationDomainByIdQuery +{ + Task GetOrganizationDomainById(Guid id); +} diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByOrganizationIdQuery.cs b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByOrganizationIdQuery.cs new file mode 100644 index 0000000000..1377cb48fb --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByOrganizationIdQuery.cs @@ -0,0 +1,8 @@ +using Bit.Core.Entities; + +namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; + +public interface IGetOrganizationDomainByOrganizationIdQuery +{ + Task> GetDomainsByOrganizationId(Guid orgId); +} diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IVerifyOrganizationDomainCommand.cs b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IVerifyOrganizationDomainCommand.cs new file mode 100644 index 0000000000..1d070cf3c1 --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IVerifyOrganizationDomainCommand.cs @@ -0,0 +1,8 @@ +using Bit.Core.Entities; + +namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; + +public interface IVerifyOrganizationDomainCommand +{ + Task VerifyOrganizationDomain(Guid id); +} diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs b/src/Core/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs new file mode 100644 index 0000000000..a2b38a3573 --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs @@ -0,0 +1,73 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Microsoft.Extensions.Logging; + +namespace Bit.Core.OrganizationFeatures.OrganizationDomains; + +public class VerifyOrganizationDomainCommand : IVerifyOrganizationDomainCommand +{ + private readonly IOrganizationDomainRepository _organizationDomainRepository; + private readonly IDnsResolverService _dnsResolverService; + private readonly IEventService _eventService; + private readonly ILogger _logger; + + public VerifyOrganizationDomainCommand( + IOrganizationDomainRepository organizationDomainRepository, + IDnsResolverService dnsResolverService, + IEventService eventService, + ILogger logger) + { + _organizationDomainRepository = organizationDomainRepository; + _dnsResolverService = dnsResolverService; + _eventService = eventService; + _logger = logger; + } + + public async Task VerifyOrganizationDomain(Guid id) + { + var domain = await _organizationDomainRepository.GetByIdAsync(id); + if (domain is null) + { + throw new NotFoundException(); + } + + if (domain.VerifiedDate is not null) + { + domain.SetLastCheckedDate(); + await _organizationDomainRepository.ReplaceAsync(domain); + throw new ConflictException("Domain has already been verified."); + } + + var claimedDomain = + await _organizationDomainRepository.GetClaimedDomainsByDomainNameAsync(domain.DomainName); + if (claimedDomain.Any()) + { + domain.SetLastCheckedDate(); + await _organizationDomainRepository.ReplaceAsync(domain); + throw new ConflictException("The domain is not available to be claimed."); + } + + try + { + if (await _dnsResolverService.ResolveAsync(domain.DomainName, domain.Txt)) + { + domain.SetVerifiedDate(); + } + } + catch (Exception e) + { + _logger.LogError("Error verifying Organization domain. {errorMessage}", e.Message); + } + + domain.SetLastCheckedDate(); + await _organizationDomainRepository.ReplaceAsync(domain); + + await _eventService.LogOrganizationDomainEventAsync(domain, + domain.VerifiedDate != null ? EventType.OrganizationDomain_Verified : EventType.OrganizationDomain_NotVerified); + return domain; + } +} diff --git a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs index b8e2a775e2..0eaf160452 100644 --- a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs +++ b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs @@ -7,6 +7,8 @@ using Bit.Core.OrganizationFeatures.OrganizationCollections; using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationConnections; using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces; +using Bit.Core.OrganizationFeatures.OrganizationDomains; +using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationLicenses; using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise; @@ -35,6 +37,7 @@ public static class OrganizationServiceCollectionExtensions services.AddOrganizationCollectionCommands(); services.AddOrganizationGroupCommands(); services.AddOrganizationLicenseCommandQueries(); + services.AddOrganizationDomainCommandsQueries(); } private static void AddOrganizationConnectionCommands(this IServiceCollection services) @@ -94,6 +97,15 @@ public static class OrganizationServiceCollectionExtensions services.AddScoped(); } + private static void AddOrganizationDomainCommandsQueries(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + } + private static void AddTokenizers(this IServiceCollection services) { services.AddSingleton>(serviceProvider => diff --git a/src/Core/Repositories/IOrganizationDomainRepository.cs b/src/Core/Repositories/IOrganizationDomainRepository.cs new file mode 100644 index 0000000000..dfe5358541 --- /dev/null +++ b/src/Core/Repositories/IOrganizationDomainRepository.cs @@ -0,0 +1,15 @@ +using Bit.Core.Entities; +using Bit.Core.Models.Data.Organizations; + +namespace Bit.Core.Repositories; + +public interface IOrganizationDomainRepository : IRepository +{ + Task> GetClaimedDomainsByDomainNameAsync(string domainName); + Task> GetDomainsByOrganizationIdAsync(Guid orgId); + Task> GetManyByNextRunDateAsync(DateTime date); + Task GetOrganizationDomainSsoDetailsAsync(string email); + Task GetDomainByOrgIdAndDomainNameAsync(Guid orgId, string domainName); + Task> GetExpiredOrganizationDomainsAsync(); + Task DeleteExpiredAsync(int expirationPeriod); +} diff --git a/src/Core/Services/IDnsResolverService.cs b/src/Core/Services/IDnsResolverService.cs new file mode 100644 index 0000000000..17e4edee92 --- /dev/null +++ b/src/Core/Services/IDnsResolverService.cs @@ -0,0 +1,6 @@ +namespace Bit.Core.Services; + +public interface IDnsResolverService +{ + Task ResolveAsync(string domain, string txtRecord, CancellationToken cancellationToken = default); +} diff --git a/src/Core/Services/IEventService.cs b/src/Core/Services/IEventService.cs index 68c0acfc6c..ec05c6943a 100644 --- a/src/Core/Services/IEventService.cs +++ b/src/Core/Services/IEventService.cs @@ -23,4 +23,6 @@ public interface IEventService Task LogProviderUserEventAsync(ProviderUser providerUser, EventType type, DateTime? date = null); Task LogProviderUsersEventAsync(IEnumerable<(ProviderUser, EventType, DateTime?)> events); Task LogProviderOrganizationEventAsync(ProviderOrganization providerOrganization, EventType type, DateTime? date = null); + Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, DateTime? date = null); + Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, EventSystemUser systemUser, DateTime? date = null); } diff --git a/src/Core/Services/IMailService.cs b/src/Core/Services/IMailService.cs index 3af89108c6..a53ac63d22 100644 --- a/src/Core/Services/IMailService.cs +++ b/src/Core/Services/IMailService.cs @@ -54,4 +54,5 @@ public interface IMailService Task SendOTPEmailAsync(string email, string token); Task SendFailedLoginAttemptsEmailAsync(string email, DateTime utcNow, string ip); Task SendFailedTwoFactorAttemptsEmailAsync(string email, DateTime utcNow, string ip); + Task SendUnverifiedOrganizationDomainEmailAsync(IEnumerable adminEmails, string organizationId, string domainName); } diff --git a/src/Core/Services/IOrganizationDomainService.cs b/src/Core/Services/IOrganizationDomainService.cs new file mode 100644 index 0000000000..87e7668ea4 --- /dev/null +++ b/src/Core/Services/IOrganizationDomainService.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Services; + +public interface IOrganizationDomainService +{ + Task ValidateOrganizationsDomainAsync(); + Task OrganizationDomainMaintenanceAsync(); +} diff --git a/src/Core/Services/Implementations/DnsResolverService.cs b/src/Core/Services/Implementations/DnsResolverService.cs new file mode 100644 index 0000000000..c47bd2e588 --- /dev/null +++ b/src/Core/Services/Implementations/DnsResolverService.cs @@ -0,0 +1,21 @@ +using Bit.Core.Exceptions; +using DnsClient; + +namespace Bit.Core.Services; + +public class DnsResolverService : IDnsResolverService +{ + public async Task ResolveAsync(string domain, string txtRecord, CancellationToken cancellationToken = default) + { + var lookup = new LookupClient(); + var result = await lookup.QueryAsync(new DnsQuestion(domain, QueryType.TXT), cancellationToken); + if (!result.HasError) + { + return result.Answers.TxtRecords() + .Select(t => t?.EscapedText?.FirstOrDefault()) + .Any(t => t == txtRecord); + } + + throw new DnsQueryException(result.ErrorMessage); + } +} diff --git a/src/Core/Services/Implementations/EventService.cs b/src/Core/Services/Implementations/EventService.cs index 82b6658e0b..f86cff76f9 100644 --- a/src/Core/Services/Implementations/EventService.cs +++ b/src/Core/Services/Implementations/EventService.cs @@ -181,7 +181,7 @@ public class EventService : IEventService continue; } - eventMessages.Add(new EventMessage(_currentContext) + var e = new EventMessage(_currentContext) { OrganizationId = group.OrganizationId, GroupId = group.Id, @@ -190,12 +190,20 @@ public class EventService : IEventService ProviderId = await GetProviderIdAsync(group.OrganizationId), SystemUser = systemUser, Date = date.GetValueOrDefault(DateTime.UtcNow) - }); + }; + + if (systemUser is EventSystemUser.SCIM) + { + // System user only used for SCIM logs in this method + // and we want event logs to report server instead of unknown + e.DeviceType = DeviceType.Server; + } + + eventMessages.Add(e); } await _eventWriteService.CreateManyAsync(eventMessages); } - public async Task LogPolicyEventAsync(Policy policy, EventType type, DateTime? date = null) { var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); @@ -247,7 +255,7 @@ public class EventService : IEventService continue; } - eventMessages.Add(new EventMessage(_currentContext) + var e = new EventMessage(_currentContext) { OrganizationId = organizationUser.OrganizationId, UserId = organizationUser.UserId, @@ -257,7 +265,16 @@ public class EventService : IEventService ActingUserId = _currentContext?.UserId, Date = date.GetValueOrDefault(DateTime.UtcNow), SystemUser = systemUser - }); + }; + + if (systemUser is EventSystemUser.SCIM) + { + // System user only used for SCIM logs in this method + // and we want event logs to report server instead of unknown + e.DeviceType = DeviceType.Server; + } + + eventMessages.Add(e); } await _eventWriteService.CreateManyAsync(eventMessages); @@ -331,6 +348,49 @@ public class EventService : IEventService await _eventWriteService.CreateAsync(e); } + public async Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, + DateTime? date = null) + { + var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); + if (!CanUseEvents(orgAbilities, organizationDomain.OrganizationId)) + { + return; + } + + var e = new EventMessage(_currentContext) + { + OrganizationId = organizationDomain.OrganizationId, + Type = type, + ActingUserId = _currentContext?.UserId, + DomainName = organizationDomain.DomainName, + Date = date.GetValueOrDefault(DateTime.UtcNow) + }; + await _eventWriteService.CreateAsync(e); + } + + public async Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, + EventSystemUser systemUser, + DateTime? date = null) + { + var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); + if (!CanUseEvents(orgAbilities, organizationDomain.OrganizationId)) + { + return; + } + + var e = new EventMessage(_currentContext) + { + OrganizationId = organizationDomain.OrganizationId, + Type = type, + ActingUserId = _currentContext?.UserId, + DomainName = organizationDomain.DomainName, + SystemUser = systemUser, + Date = date.GetValueOrDefault(DateTime.UtcNow), + DeviceType = DeviceType.Server + }; + await _eventWriteService.CreateAsync(e); + } + private async Task GetProviderIdAsync(Guid? orgId) { if (_currentContext == null || !orgId.HasValue) @@ -360,6 +420,6 @@ public class EventService : IEventService private bool CanUseProviderEvents(IDictionary providerAbilities, Guid providerId) { return providerAbilities != null && providerAbilities.ContainsKey(providerId) && - providerAbilities[providerId].Enabled && providerAbilities[providerId].UseEvents; + providerAbilities[providerId].Enabled && providerAbilities[providerId].UseEvents; } } diff --git a/src/Core/Services/Implementations/HandlebarsMailService.cs b/src/Core/Services/Implementations/HandlebarsMailService.cs index 3688c8f153..f4190593dc 100644 --- a/src/Core/Services/Implementations/HandlebarsMailService.cs +++ b/src/Core/Services/Implementations/HandlebarsMailService.cs @@ -881,6 +881,19 @@ public class HandlebarsMailService : IMailService await _mailDeliveryService.SendEmailAsync(message); } + public async Task SendUnverifiedOrganizationDomainEmailAsync(IEnumerable adminEmails, string organizationId, string domainName) + { + var message = CreateDefaultMessage("Domain not verified", adminEmails); + var model = new OrganizationDomainUnverifiedViewModel + { + Url = $"{_globalSettings.BaseServiceUri.VaultWithHash}/organizations/{organizationId}/settings/domain-verification", + DomainName = domainName + }; + await AddMessageContentAsync(message, "OrganizationDomainUnverified", model); + message.Category = "UnverifiedOrganizationDomain"; + await _mailDeliveryService.SendEmailAsync(message); + } + private static string GetUserIdentifier(string email, string userName) { return string.IsNullOrEmpty(userName) ? email : CoreHelpers.SanitizeForEmail(userName, false); diff --git a/src/Core/Services/Implementations/OrganizationDomainService.cs b/src/Core/Services/Implementations/OrganizationDomainService.cs new file mode 100644 index 0000000000..ba342ce034 --- /dev/null +++ b/src/Core/Services/Implementations/OrganizationDomainService.cs @@ -0,0 +1,139 @@ +using Bit.Core.Enums; +using Bit.Core.Repositories; +using Bit.Core.Settings; +using Microsoft.Extensions.Logging; + +namespace Bit.Core.Services; + +public class OrganizationDomainService : IOrganizationDomainService +{ + private readonly IOrganizationDomainRepository _domainRepository; + private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly IDnsResolverService _dnsResolverService; + private readonly IEventService _eventService; + private readonly IMailService _mailService; + private readonly ILogger _logger; + private readonly IGlobalSettings _globalSettings; + + public OrganizationDomainService( + IOrganizationDomainRepository domainRepository, + IOrganizationUserRepository organizationUserRepository, + IDnsResolverService dnsResolverService, + IEventService eventService, + IMailService mailService, + ILogger logger, + IGlobalSettings globalSettings) + { + _domainRepository = domainRepository; + _organizationUserRepository = organizationUserRepository; + _dnsResolverService = dnsResolverService; + _eventService = eventService; + _mailService = mailService; + _logger = logger; + _globalSettings = globalSettings; + } + + public async Task ValidateOrganizationsDomainAsync() + { + //Date should be set 1 hour behind to ensure it selects all domains that should be verified + var runDate = DateTime.UtcNow.AddHours(-1); + + var verifiableDomains = await _domainRepository.GetManyByNextRunDateAsync(runDate); + + _logger.LogInformation(Constants.BypassFiltersEventId, "Validating {verifiableDomainsCount} domains.", verifiableDomains.Count); + + foreach (var domain in verifiableDomains) + { + try + { + _logger.LogInformation(Constants.BypassFiltersEventId, "Attempting verification for organization {OrgId} with domain {Domain}", domain.OrganizationId, domain.DomainName); + + var status = await _dnsResolverService.ResolveAsync(domain.DomainName, domain.Txt); + if (status) + { + _logger.LogInformation(Constants.BypassFiltersEventId, "Successfully validated domain"); + + //update entry on OrganizationDomain table + domain.SetLastCheckedDate(); + domain.SetVerifiedDate(); + domain.SetJobRunCount(); + await _domainRepository.ReplaceAsync(domain); + + await _eventService.LogOrganizationDomainEventAsync(domain, EventType.OrganizationDomain_Verified, + EventSystemUser.DomainVerification); + } + else + { + //update entry on OrganizationDomain table + domain.SetLastCheckedDate(); + domain.SetJobRunCount(); + domain.SetNextRunDate(_globalSettings.DomainVerification.VerificationInterval); + await _domainRepository.ReplaceAsync(domain); + + await _eventService.LogOrganizationDomainEventAsync(domain, EventType.OrganizationDomain_NotVerified, + EventSystemUser.DomainVerification); + _logger.LogInformation(Constants.BypassFiltersEventId, "Verification for organization {OrgId} with domain {Domain} failed", + domain.OrganizationId, domain.DomainName); + } + } + catch (Exception ex) + { + //update entry on OrganizationDomain table + domain.SetLastCheckedDate(); + domain.SetJobRunCount(); + domain.SetNextRunDate(_globalSettings.DomainVerification.VerificationInterval); + await _domainRepository.ReplaceAsync(domain); + + await _eventService.LogOrganizationDomainEventAsync(domain, EventType.OrganizationDomain_NotVerified, + EventSystemUser.DomainVerification); + + _logger.LogError(ex, "Verification for organization {OrgId} with domain {Domain} threw an exception: {errorMessage}", + domain.OrganizationId, domain.DomainName, ex.Message); + } + } + } + + public async Task OrganizationDomainMaintenanceAsync() + { + try + { + //Get domains that have not been verified within 72 hours + var expiredDomains = await _domainRepository.GetExpiredOrganizationDomainsAsync(); + + _logger.LogInformation(Constants.BypassFiltersEventId, + "Attempting email reminder for {expiredDomainCount} expired domains.", expiredDomains.Count); + + foreach (var domain in expiredDomains) + { + //get admin emails of organization + var adminEmails = await GetAdminEmailsAsync(domain.OrganizationId); + + //Send email to administrators + if (adminEmails.Count > 0) + { + await _mailService.SendUnverifiedOrganizationDomainEmailAsync(adminEmails, + domain.OrganizationId.ToString(), domain.DomainName); + } + + _logger.LogInformation(Constants.BypassFiltersEventId, "Expired domain: {domainName}", domain.DomainName); + } + //delete domains that have not been verified within 7 days + var status = await _domainRepository.DeleteExpiredAsync(_globalSettings.DomainVerification.ExpirationPeriod); + _logger.LogInformation(Constants.BypassFiltersEventId, "Delete status {status}", status); + } + catch (Exception ex) + { + _logger.LogError(ex, "Organization domain maintenance failed"); + } + } + + private async Task> GetAdminEmailsAsync(Guid organizationId) + { + var orgUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(organizationId); + var emailList = orgUsers.Where(o => o.Type <= OrganizationUserType.Admin + || o.GetPermissions()?.ManageSso == true) + .Select(a => a.Email).Distinct().ToList(); + + return emailList; + } +} diff --git a/src/Core/Services/NoopImplementations/NoopEventService.cs b/src/Core/Services/NoopImplementations/NoopEventService.cs index 7f9d02b949..a44fdf3e2c 100644 --- a/src/Core/Services/NoopImplementations/NoopEventService.cs +++ b/src/Core/Services/NoopImplementations/NoopEventService.cs @@ -68,6 +68,19 @@ public class NoopEventService : IEventService return Task.FromResult(0); } + public Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, + DateTime? date = null) + { + return Task.FromResult(0); + } + + public Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, + EventSystemUser systemUser, + DateTime? date = null) + { + return Task.FromResult(0); + } + public Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type, DateTime? date = null) { return Task.FromResult(0); diff --git a/src/Core/Services/NoopImplementations/NoopMailService.cs b/src/Core/Services/NoopImplementations/NoopMailService.cs index cee8c91f42..36fb175ab4 100644 --- a/src/Core/Services/NoopImplementations/NoopMailService.cs +++ b/src/Core/Services/NoopImplementations/NoopMailService.cs @@ -237,4 +237,9 @@ public class NoopMailService : IMailService { return Task.FromResult(0); } + + public Task SendUnverifiedOrganizationDomainEmailAsync(IEnumerable adminEmails, string organizationId, string domainName) + { + return Task.FromResult(0); + } } diff --git a/src/Core/Settings/GlobalSettings.cs b/src/Core/Settings/GlobalSettings.cs index 53261fa2ab..6a1198fc1e 100644 --- a/src/Core/Settings/GlobalSettings.cs +++ b/src/Core/Settings/GlobalSettings.cs @@ -77,6 +77,7 @@ public class GlobalSettings : IGlobalSettings public virtual DistributedIpRateLimitingSettings DistributedIpRateLimiting { get; set; } = new DistributedIpRateLimitingSettings(); public virtual IPasswordlessAuthSettings PasswordlessAuth { get; set; } = new PasswordlessAuthSettings(); + public virtual IDomainVerificationSettings DomainVerification { get; set; } = new DomainVerificationSettings(); public string BuildExternalUri(string explicitValue, string name) { @@ -537,4 +538,10 @@ public class GlobalSettings : IGlobalSettings { public bool KnownDevicesOnly { get; set; } = true; } + + public class DomainVerificationSettings : IDomainVerificationSettings + { + public int VerificationInterval { get; set; } = 12; + public int ExpirationPeriod { get; set; } = 7; + } } diff --git a/src/Core/Settings/IDomainVerificationSettings.cs b/src/Core/Settings/IDomainVerificationSettings.cs new file mode 100644 index 0000000000..aac6c70046 --- /dev/null +++ b/src/Core/Settings/IDomainVerificationSettings.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Settings; + +public interface IDomainVerificationSettings +{ + public int VerificationInterval { get; set; } + public int ExpirationPeriod { get; set; } +} diff --git a/src/Core/Settings/IGlobalSettings.cs b/src/Core/Settings/IGlobalSettings.cs index 9ed58669ca..724c595299 100644 --- a/src/Core/Settings/IGlobalSettings.cs +++ b/src/Core/Settings/IGlobalSettings.cs @@ -17,4 +17,5 @@ public interface IGlobalSettings ISsoSettings Sso { get; set; } ILogLevelSettings MinLogLevel { get; set; } IPasswordlessAuthSettings PasswordlessAuth { get; set; } + IDomainVerificationSettings DomainVerification { get; set; } } diff --git a/src/Core/packages.lock.json b/src/Core/packages.lock.json index 8af14081d8..3d85dc3785 100644 --- a/src/Core/packages.lock.json +++ b/src/Core/packages.lock.json @@ -93,6 +93,15 @@ "System.Xml.XPath.XmlDocument": "4.3.0" } }, + "DnsClient": { + "type": "Direct", + "requested": "[1.7.0, )", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2.AspNet": { "type": "Direct", "requested": "[3.0.1, )", diff --git a/src/Events/packages.lock.json b/src/Events/packages.lock.json index 51058788bf..bfb0051b19 100644 --- a/src/Events/packages.lock.json +++ b/src/Events/packages.lock.json @@ -148,6 +148,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2730,6 +2738,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/src/EventsProcessor/packages.lock.json b/src/EventsProcessor/packages.lock.json index 51058788bf..bfb0051b19 100644 --- a/src/EventsProcessor/packages.lock.json +++ b/src/EventsProcessor/packages.lock.json @@ -148,6 +148,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2730,6 +2738,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/src/Icons/packages.lock.json b/src/Icons/packages.lock.json index 7ff4b356d9..a09e93cb2f 100644 --- a/src/Icons/packages.lock.json +++ b/src/Icons/packages.lock.json @@ -158,6 +158,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2740,6 +2748,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/src/Identity/packages.lock.json b/src/Identity/packages.lock.json index 6ae150fce6..cb0378ca95 100644 --- a/src/Identity/packages.lock.json +++ b/src/Identity/packages.lock.json @@ -157,6 +157,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2752,6 +2760,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs index 7d1231190e..44e67f261b 100644 --- a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs +++ b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs @@ -38,6 +38,7 @@ public static class DapperServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); if (selfHosted) { diff --git a/src/Infrastructure.Dapper/Repositories/OrganizationDomainRepository.cs b/src/Infrastructure.Dapper/Repositories/OrganizationDomainRepository.cs new file mode 100644 index 0000000000..c46c994a33 --- /dev/null +++ b/src/Infrastructure.Dapper/Repositories/OrganizationDomainRepository.cs @@ -0,0 +1,110 @@ +using System.Data; +using Bit.Core.Entities; +using Bit.Core.Models.Data.Organizations; +using Bit.Core.Repositories; +using Bit.Core.Settings; +using Dapper; +using Microsoft.Data.SqlClient; + +namespace Bit.Infrastructure.Dapper.Repositories; + +public class OrganizationDomainRepository : Repository, IOrganizationDomainRepository +{ + public OrganizationDomainRepository(GlobalSettings globalSettings) + : this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString) + { } + + public OrganizationDomainRepository(string connectionString, string readOnlyConnectionString) + : base(connectionString, readOnlyConnectionString) + { } + + public async Task> GetClaimedDomainsByDomainNameAsync(string domainName) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + $"[{Schema}].[OrganizationDomain_ReadByClaimedDomain]", + new { DomainName = domainName }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } + + public async Task> GetDomainsByOrganizationIdAsync(Guid orgId) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + $"[{Schema}].[OrganizationDomain_ReadByOrganizationId]", + new { OrganizationId = orgId }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } + + public async Task> GetManyByNextRunDateAsync(DateTime date) + { + using var connection = new SqlConnection(ConnectionString); + var results = await connection.QueryAsync( + $"[{Schema}].[OrganizationDomain_ReadByNextRunDate]", + new { Date = date }, commandType: CommandType.StoredProcedure + ); + + return results.ToList(); + } + + public async Task GetOrganizationDomainSsoDetailsAsync(string email) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection + .QueryAsync( + $"[{Schema}].[OrganizationDomainSsoDetails_ReadByEmail]", + new { Email = email }, + commandType: CommandType.StoredProcedure); + + return results.SingleOrDefault(); + } + } + + public async Task GetDomainByOrgIdAndDomainNameAsync(Guid orgId, string domainName) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection + .QueryAsync( + $"[{Schema}].[OrganizationDomain_ReadDomainByOrgIdAndDomainName]", + new { OrganizationId = orgId, DomainName = domainName }, + commandType: CommandType.StoredProcedure); + + return results.SingleOrDefault(); + } + } + + public async Task> GetExpiredOrganizationDomainsAsync() + { + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection + .QueryAsync( + $"[{Schema}].[OrganizationDomain_ReadIfExpired]", + null, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } + + public async Task DeleteExpiredAsync(int expirationPeriod) + { + using (var connection = new SqlConnection(ConnectionString)) + { + return await connection.ExecuteAsync( + $"[{Schema}].[OrganizationDomain_DeleteIfExpired]", + new { ExpirationPeriod = expirationPeriod }, + commandType: CommandType.StoredProcedure) > 0; + } + } +} diff --git a/src/Infrastructure.Dapper/packages.lock.json b/src/Infrastructure.Dapper/packages.lock.json index e0adb36cba..7b050ad0f3 100644 --- a/src/Infrastructure.Dapper/packages.lock.json +++ b/src/Infrastructure.Dapper/packages.lock.json @@ -132,6 +132,14 @@ "System.Xml.XPath.XmlDocument": "4.3.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2559,6 +2567,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs index d40c770da1..2ec4fca7e8 100644 --- a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs +++ b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs @@ -73,6 +73,7 @@ public static class EntityFrameworkServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); if (selfHosted) { diff --git a/src/Infrastructure.EntityFramework/Models/Organization.cs b/src/Infrastructure.EntityFramework/Models/Organization.cs index c1969cab0c..49f9ff422c 100644 --- a/src/Infrastructure.EntityFramework/Models/Organization.cs +++ b/src/Infrastructure.EntityFramework/Models/Organization.cs @@ -13,6 +13,7 @@ public class Organization : Core.Entities.Organization public virtual ICollection Transactions { get; set; } public virtual ICollection ApiKeys { get; set; } public virtual ICollection Connections { get; set; } + public virtual ICollection Domains { get; set; } } public class OrganizationMapperProfile : Profile diff --git a/src/Infrastructure.EntityFramework/Models/OrganizationDomain.cs b/src/Infrastructure.EntityFramework/Models/OrganizationDomain.cs new file mode 100644 index 0000000000..bef7ba2af6 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Models/OrganizationDomain.cs @@ -0,0 +1,16 @@ +using AutoMapper; + +namespace Bit.Infrastructure.EntityFramework.Models; + +public class OrganizationDomain : Core.Entities.OrganizationDomain +{ + public virtual Organization Organization { get; set; } +} + +public class OrganizationDomainMapperProfile : Profile +{ + public OrganizationDomainMapperProfile() + { + CreateMap().ReverseMap(); + } +} diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs index 9aeb18dc15..e782960d89 100644 --- a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs +++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs @@ -56,6 +56,7 @@ public class DatabaseContext : DbContext public DbSet Transactions { get; set; } public DbSet Users { get; set; } public DbSet AuthRequests { get; set; } + public DbSet OrganizationDomains { get; set; } protected override void OnModelCreating(ModelBuilder builder) { @@ -95,6 +96,7 @@ public class DatabaseContext : DbContext var eOrganizationApiKey = builder.Entity(); var eOrganizationConnection = builder.Entity(); var eAuthRequest = builder.Entity(); + var eOrganizationDomain = builder.Entity(); eCipher.Property(c => c.Id).ValueGeneratedNever(); eCollection.Property(c => c.Id).ValueGeneratedNever(); @@ -116,6 +118,7 @@ public class DatabaseContext : DbContext eOrganizationApiKey.Property(c => c.Id).ValueGeneratedNever(); eOrganizationConnection.Property(c => c.Id).ValueGeneratedNever(); eAuthRequest.Property(ar => ar.Id).ValueGeneratedNever(); + eOrganizationDomain.Property(ar => ar.Id).ValueGeneratedNever(); eCollectionCipher.HasKey(cc => new { cc.CollectionId, cc.CipherId }); eCollectionUser.HasKey(cu => new { cu.CollectionId, cu.OrganizationUserId }); @@ -167,6 +170,7 @@ public class DatabaseContext : DbContext eOrganizationApiKey.ToTable(nameof(OrganizationApiKey)); eOrganizationConnection.ToTable(nameof(OrganizationConnection)); eAuthRequest.ToTable(nameof(AuthRequest)); + eOrganizationDomain.ToTable(nameof(OrganizationDomain)); ConfigureDateTimeUtcQueries(builder); } diff --git a/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs b/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs new file mode 100644 index 0000000000..daafd7c958 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs @@ -0,0 +1,147 @@ +using System.Net.Mail; +using AutoMapper; +using Bit.Core.Enums; +using Bit.Core.Models.Data.Organizations; +using Bit.Core.Repositories; +using Bit.Infrastructure.EntityFramework.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Infrastructure.EntityFramework.Repositories; + +public class OrganizationDomainRepository : Repository, IOrganizationDomainRepository +{ + public OrganizationDomainRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) + : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.OrganizationDomains) + { + } + + public async Task> GetClaimedDomainsByDomainNameAsync( + string domainName) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var claimedDomains = await dbContext.OrganizationDomains + .Where(x => x.DomainName == domainName + && x.VerifiedDate != null) + .AsNoTracking() + .ToListAsync(); + return Mapper.Map>(claimedDomains); + } + + public async Task> GetDomainsByOrganizationIdAsync(Guid orgId) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var domains = await dbContext.OrganizationDomains + .Where(x => x.OrganizationId == orgId) + .AsNoTracking() + .ToListAsync(); + return Mapper.Map>(domains); + } + + public async Task> GetManyByNextRunDateAsync(DateTime date) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + + var domains = await dbContext.OrganizationDomains + .Where(x => x.VerifiedDate == null + && x.JobRunCount != 3 + && x.NextRunDate.Year == date.Year + && x.NextRunDate.Month == date.Month + && x.NextRunDate.Day == date.Day + && x.NextRunDate.Hour == date.Hour) + .AsNoTracking() + .ToListAsync(); + + //Get records that have ignored/failed by the background service + var pastDomains = dbContext.OrganizationDomains + .AsEnumerable() + .Where(x => (date - x.NextRunDate).TotalHours > 36 + && x.VerifiedDate == null + && x.JobRunCount != 3) + .ToList(); + + var results = domains.Union(pastDomains); + + return Mapper.Map>(results); + } + + public async Task GetOrganizationDomainSsoDetailsAsync(string email) + { + var domainName = new MailAddress(email).Host; + + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var ssoDetails = await dbContext.Organizations + .Join(dbContext.OrganizationDomains, o => o.Id, od => od.OrganizationId, + (organization, domain) => new { resOrganization = organization, resDomain = domain }) + .Join(dbContext.Policies, o => o.resOrganization.Id, p => p.OrganizationId, + (combinedOrgDomain, policy) + => new + { + Organization = combinedOrgDomain.resOrganization, + Domain = combinedOrgDomain.resDomain, + Policy = policy + }) + .Select(x => new OrganizationDomainSsoDetailsData + { + OrganizationId = x.Organization.Id, + OrganizationName = x.Organization.Name, + SsoAvailable = x.Organization.UseSso, + OrganizationIdentifier = x.Organization.Identifier, + SsoRequired = x.Policy.Enabled, + VerifiedDate = x.Domain.VerifiedDate, + PolicyType = x.Policy.Type, + DomainName = x.Domain.DomainName, + OrganizationEnabled = x.Organization.Enabled + }) + .Where(y => y.DomainName == domainName + && y.OrganizationEnabled == true + && y.PolicyType.Equals(PolicyType.RequireSso)) + .AsNoTracking() + .SingleOrDefaultAsync(); + + return ssoDetails; + } + + public async Task GetDomainByOrgIdAndDomainNameAsync(Guid orgId, string domainName) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var domain = await dbContext.OrganizationDomains + .Where(x => x.OrganizationId == orgId && x.DomainName == domainName) + .AsNoTracking() + .FirstOrDefaultAsync(); + + return Mapper.Map(domain); + } + + public async Task> GetExpiredOrganizationDomainsAsync() + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + + //Get domains that have not been verified after 72 hours + var domains = dbContext.OrganizationDomains + .AsEnumerable() + .Where(x => (DateTime.UtcNow - x.CreationDate).Days >= 4 + && x.VerifiedDate == null) + .ToList(); + + return Mapper.Map>(domains); + } + + public async Task DeleteExpiredAsync(int expirationPeriod) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + + var expiredDomains = await dbContext.OrganizationDomains + .Where(x => x.LastCheckedDate < DateTime.UtcNow.AddDays(-expirationPeriod)) + .ToListAsync(); + dbContext.OrganizationDomains.RemoveRange(expiredDomains); + return await dbContext.SaveChangesAsync() > 0; + } +} diff --git a/src/Infrastructure.EntityFramework/packages.lock.json b/src/Infrastructure.EntityFramework/packages.lock.json index 436512dacd..5a1316b527 100644 --- a/src/Infrastructure.EntityFramework/packages.lock.json +++ b/src/Infrastructure.EntityFramework/packages.lock.json @@ -207,6 +207,14 @@ "System.Xml.XPath.XmlDocument": "4.3.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2732,6 +2740,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/src/Notifications/packages.lock.json b/src/Notifications/packages.lock.json index 9def1f8d36..42fef74678 100644 --- a/src/Notifications/packages.lock.json +++ b/src/Notifications/packages.lock.json @@ -169,6 +169,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2780,6 +2788,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index 95537a10c9..e86ae31211 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -124,6 +124,7 @@ public static class ServiceCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddLoginServices(); + services.AddScoped(); } public static void AddTokenizers(this IServiceCollection services) @@ -173,6 +174,7 @@ public static class ServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddTokenizers(); if (CoreHelpers.SettingHasValue(globalSettings.ServiceBus.ConnectionString) && diff --git a/src/SharedWeb/packages.lock.json b/src/SharedWeb/packages.lock.json index 6ba839fea5..fef8c5d43e 100644 --- a/src/SharedWeb/packages.lock.json +++ b/src/SharedWeb/packages.lock.json @@ -148,6 +148,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2730,6 +2738,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 9d7db8137d..366026063c 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -454,4 +454,20 @@ + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Event_Create.sql b/src/Sql/dbo/Stored Procedures/Event_Create.sql index 99525ed0d5..910e0e5988 100644 --- a/src/Sql/dbo/Stored Procedures/Event_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Event_Create.sql @@ -16,7 +16,8 @@ @DeviceType SMALLINT, @IpAddress VARCHAR(50), @Date DATETIME2(7), - @SystemUser TINYINT = null + @SystemUser TINYINT = null, + @DomainName VARCHAR(256) AS BEGIN SET NOCOUNT ON @@ -40,7 +41,8 @@ BEGIN [DeviceType], [IpAddress], [Date], - [SystemUser] + [SystemUser], + [DomainName] ) VALUES ( @@ -61,6 +63,7 @@ BEGIN @DeviceType, @IpAddress, @Date, - @SystemUser + @SystemUser, + @DomainName ) END diff --git a/src/Sql/dbo/Stored Procedures/OrganizationDomainSsoDetails_ReadByEmail.sql b/src/Sql/dbo/Stored Procedures/OrganizationDomainSsoDetails_ReadByEmail.sql new file mode 100644 index 0000000000..0a2964d655 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationDomainSsoDetails_ReadByEmail.sql @@ -0,0 +1,29 @@ +CREATE PROCEDURE [dbo].[OrganizationDomainSsoDetails_ReadByEmail] + @Email NVARCHAR(256) +AS +BEGIN + SET NOCOUNT ON + + DECLARE @Domain NVARCHAR(256) + + SELECT @Domain = SUBSTRING(@Email, CHARINDEX( '@', @Email) + 1, LEN(@Email)) + + SELECT + O.Id AS OrganizationId, + O.[Name] AS OrganizationName, + O.UseSso AS SsoAvailable, + P.Enabled AS SsoRequired, + O.Identifier AS OrganizationIdentifier, + OD.VerifiedDate, + P.[Type] AS PolicyType, + OD.DomainName + FROM + [dbo].[OrganizationView] O + INNER JOIN [dbo].[OrganizationDomainView] OD + ON O.Id = OD.OrganizationId + LEFT JOIN [dbo].[PolicyView] P + ON O.Id = P.OrganizationId + WHERE OD.DomainName = @Domain + AND O.Enabled = 1 + AND (P.Id is NULL OR (P.Id IS NOT NULL AND P.[Type] = 4)) -- SSO Type +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/OrganizationDomain_Create.sql b/src/Sql/dbo/Stored Procedures/OrganizationDomain_Create.sql new file mode 100644 index 0000000000..af6f5848a0 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationDomain_Create.sql @@ -0,0 +1,39 @@ +CREATE PROCEDURE [dbo].[OrganizationDomain_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Txt VARCHAR(MAX), + @DomainName NVARCHAR(255), + @CreationDate DATETIME2(7), + @VerifiedDate DATETIME2(7), + @LastCheckedDate DATETIME2(7), + @NextRunDate DATETIME2(7), + @JobRunCount TINYINT +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[OrganizationDomain] + ( + [Id], + [OrganizationId], + [Txt], + [DomainName], + [CreationDate], + [VerifiedDate], + [LastCheckedDate], + [NextRunDate], + [JobRunCount] + ) + VALUES + ( + @Id, + @OrganizationId, + @Txt, + @DomainName, + @CreationDate, + @VerifiedDate, + @LastCheckedDate, + @NextRunDate, + @JobRunCount + ) +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/OrganizationDomain_DeleteById.sql b/src/Sql/dbo/Stored Procedures/OrganizationDomain_DeleteById.sql new file mode 100644 index 0000000000..e7637b95b5 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationDomain_DeleteById.sql @@ -0,0 +1,12 @@ +CREATE PROCEDURE [dbo].[OrganizationDomain_DeleteById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + DELETE + FROM + [dbo].[OrganizationDomain] + WHERE + [Id] = @Id +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/OrganizationDomain_DeleteIfExpired.sql b/src/Sql/dbo/Stored Procedures/OrganizationDomain_DeleteIfExpired.sql new file mode 100644 index 0000000000..3e42b9bc66 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationDomain_DeleteIfExpired.sql @@ -0,0 +1,10 @@ +CREATE PROCEDURE [dbo].[OrganizationDomain_DeleteIfExpired] + @ExpirationPeriod TINYINT +AS +BEGIN + SET NOCOUNT OFF + + DELETE FROM [dbo].[OrganizationDomain] + WHERE DATEDIFF(DAY, [LastCheckedDate], GETUTCDATE()) >= @ExpirationPeriod + AND [VerifiedDate] IS NULL +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/OrganizationDomain_OrganizationDeleted.sql b/src/Sql/dbo/Stored Procedures/OrganizationDomain_OrganizationDeleted.sql new file mode 100644 index 0000000000..8800a31c21 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationDomain_OrganizationDeleted.sql @@ -0,0 +1,12 @@ +CREATE PROCEDURE [dbo].[OrganizationDomain_OrganizationDeleted] + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + DELETE + FROM + [dbo].[OrganizationDomain] + WHERE + [OrganizationId] = @OrganizationId +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadByClaimedDomain.sql b/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadByClaimedDomain.sql new file mode 100644 index 0000000000..3dd2424375 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadByClaimedDomain.sql @@ -0,0 +1,15 @@ +CREATE PROCEDURE [dbo].[OrganizationDomain_ReadByClaimedDomain] + @DomainName NVARCHAR(255) +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[OrganizationDomain] + WHERE + [DomainName] = @DomainName + AND + [VerifiedDate] IS NOT NULL +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadById.sql b/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadById.sql new file mode 100644 index 0000000000..ae6f98bfd8 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadById.sql @@ -0,0 +1,13 @@ +CREATE PROCEDURE [dbo].[OrganizationDomain_ReadById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[OrganizationDomain] + WHERE + [Id] = @Id +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadByNextRunDate.sql b/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadByNextRunDate.sql new file mode 100644 index 0000000000..7dc51c77d0 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadByNextRunDate.sql @@ -0,0 +1,25 @@ +CREATE PROCEDURE [dbo].[OrganizationDomain_ReadByNextRunDate] + @Date DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[OrganizationDomain] + WHERE [VerifiedDate] IS NULL + AND [JobRunCount] != 3 + AND DATEPART(year, [NextRunDate]) = DATEPART(year, @Date) + AND DATEPART(month, [NextRunDate]) = DATEPART(month, @Date) + AND DATEPART(day, [NextRunDate]) = DATEPART(day, @Date) + AND DATEPART(hour, [NextRunDate]) = DATEPART(hour, @Date) + UNION + SELECT + * + FROM + [dbo].[OrganizationDomain] + WHERE DATEDIFF(hour, [NextRunDate], @Date) > 36 + AND [VerifiedDate] IS NULL + AND [JobRunCount] != 3 +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadByOrganizationId.sql b/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadByOrganizationId.sql new file mode 100644 index 0000000000..e9e723ab6c --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadByOrganizationId.sql @@ -0,0 +1,13 @@ +CREATE PROCEDURE [dbo].[OrganizationDomain_ReadByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[OrganizationDomain] + WHERE + [OrganizationId] = @OrganizationId +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadDomainByOrgIdAndDomainName.sql b/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadDomainByOrgIdAndDomainName.sql new file mode 100644 index 0000000000..6cc19f965d --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadDomainByOrgIdAndDomainName.sql @@ -0,0 +1,16 @@ +CREATE PROCEDURE [dbo].[OrganizationDomain_ReadDomainByOrgIdAndDomainName] + @OrganizationId UNIQUEIDENTIFIER, + @DomainName NVARCHAR(255) +AS +BEGIN + SET NOCOUNT ON + +SELECT + * +FROM + [dbo].[OrganizationDomain] +WHERE + [OrganizationId] = @OrganizationId + AND + [DomainName] = @DomainName +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadIfExpired.sql b/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadIfExpired.sql new file mode 100644 index 0000000000..d548c97b99 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadIfExpired.sql @@ -0,0 +1,13 @@ +CREATE PROCEDURE [dbo].[OrganizationDomain_ReadIfExpired] +AS +BEGIN + SET NOCOUNT OFF + + SELECT + * + FROM + [dbo].[OrganizationDomain] + WHERE + DATEDIFF(DAY, [CreationDate], GETUTCDATE()) >= 4 --Get domains that have not been verified after 3 days (72 hours) + AND + [VerifiedDate] IS NULL \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/OrganizationDomain_Update.sql b/src/Sql/dbo/Stored Procedures/OrganizationDomain_Update.sql new file mode 100644 index 0000000000..7318f9afb9 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationDomain_Update.sql @@ -0,0 +1,28 @@ +CREATE PROCEDURE [dbo].[OrganizationDomain_Update] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Txt VARCHAR(MAX), + @DomainName NVARCHAR(255), + @CreationDate DATETIME2(7), + @VerifiedDate DATETIME2(7), + @LastCheckedDate DATETIME2(7), + @NextRunDate DATETIME2(7), + @JobRunCount TINYINT +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[OrganizationDomain] + SET + [OrganizationId] = @OrganizationId, + [Txt] = @Txt, + [DomainName] = @DomainName, + [CreationDate] = @CreationDate, + [VerifiedDate] = @VerifiedDate, + [LastCheckedDate] = @LastCheckedDate + [NextRunDate] = @NextRunDate, + [JobRunCount] = @JobRunCount + WHERE + [Id] = @Id +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Organization_DeleteById.sql b/src/Sql/dbo/Stored Procedures/Organization_DeleteById.sql index c54a08c029..2c911ea76d 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_DeleteById.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_DeleteById.sql @@ -60,6 +60,7 @@ BEGIN EXEC [dbo].[OrganizationApiKey_OrganizationDeleted] @Id EXEC [dbo].[OrganizationConnection_OrganizationDeleted] @Id EXEC [dbo].[OrganizationSponsorship_OrganizationDeleted] @Id + EXEC [dbo].[OrganizationDomain_OrganizationDeleted] @Id DELETE FROM diff --git a/src/Sql/dbo/Tables/Event.sql b/src/Sql/dbo/Tables/Event.sql index 9972f41294..5181d67686 100644 --- a/src/Sql/dbo/Tables/Event.sql +++ b/src/Sql/dbo/Tables/Event.sql @@ -17,6 +17,7 @@ [ProviderUserId] UNIQUEIDENTIFIER NULL, [ProviderOrganizationId] UNIQUEIDENTIFIER NULL, [SystemUser] TINYINT NULL, + [DomainName] VARCHAR(256) NULL CONSTRAINT [PK_Event] PRIMARY KEY CLUSTERED ([Id] ASC) ); diff --git a/src/Sql/dbo/Tables/OrganizationDomain.sql b/src/Sql/dbo/Tables/OrganizationDomain.sql new file mode 100644 index 0000000000..d7585167a6 --- /dev/null +++ b/src/Sql/dbo/Tables/OrganizationDomain.sql @@ -0,0 +1,15 @@ +CREATE TABLE [dbo].[OrganizationDomain] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [OrganizationId] UNIQUEIDENTIFIER NOT NULL, + [Txt] VARCHAR(MAX) NOT NULL, + [DomainName] NVARCHAR(255) NOT NULL, + [CreationDate] DATETIME2(7) NOT NULL, + [VerifiedDate] DATETIME2(7) NULL, + [LastCheckedDate] DATETIME2(7) NULL, + [NextRunDate] DATETIME2(7) NOT NULL, + [JobRunCount] TINYINT NOT NULL + CONSTRAINT [PK_OrganizationDomain] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_OrganzationDomain_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) +); + +GO \ No newline at end of file diff --git a/src/Sql/dbo/Views/OrganizationDomainView.sql b/src/Sql/dbo/Views/OrganizationDomainView.sql new file mode 100644 index 0000000000..c5d484866d --- /dev/null +++ b/src/Sql/dbo/Views/OrganizationDomainView.sql @@ -0,0 +1,6 @@ +CREATE VIEW [dbo].[OrganizationDomainView] +AS +SELECT + * +FROM + [dbo].[OrganizationDomain] \ No newline at end of file diff --git a/test/Api.IntegrationTest/packages.lock.json b/test/Api.IntegrationTest/packages.lock.json index a711f75622..97e7c7b97d 100644 --- a/test/Api.IntegrationTest/packages.lock.json +++ b/test/Api.IntegrationTest/packages.lock.json @@ -235,6 +235,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fare": { "type": "Transitive", "resolved": "2.1.1", @@ -3193,6 +3201,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/test/Api.Test/Controllers/OrganizationDomainControllerTests.cs b/test/Api.Test/Controllers/OrganizationDomainControllerTests.cs new file mode 100644 index 0000000000..678b38776c --- /dev/null +++ b/test/Api.Test/Controllers/OrganizationDomainControllerTests.cs @@ -0,0 +1,268 @@ +using Bit.Api.Controllers; +using Bit.Api.Models.Request; +using Bit.Api.Models.Request.Organizations; +using Bit.Api.Models.Response; +using Bit.Api.Models.Response.Organizations; +using Bit.Core.Context; +using Bit.Core.Exceptions; +using Bit.Core.Models.Data.Organizations; +using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; +using Bit.Core.Repositories; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using NSubstitute.ReturnsExtensions; +using Xunit; +using Organization = Bit.Core.Entities.Organization; +using OrganizationDomain = Bit.Core.Entities.OrganizationDomain; + +namespace Bit.Api.Test.Controllers; + +[ControllerCustomize(typeof(OrganizationDomainController))] +[SutProviderCustomize] +public class OrganizationDomainControllerTests +{ + [Theory, BitAutoData] + public async Task Get_ShouldThrowUnauthorized_WhenOrgIdCannotManageSso(Guid orgId, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(orgId).Returns(false); + + var requestAction = async () => await sutProvider.Sut.Get(orgId.ToString()); + + await Assert.ThrowsAsync(requestAction); + } + + [Theory, BitAutoData] + public async Task Get_ShouldNotFound_WhenOrganizationDoesNotExist(Guid orgId, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(orgId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(orgId).ReturnsNull(); + + var requestAction = async () => await sutProvider.Sut.Get(orgId.ToString()); + + await Assert.ThrowsAsync(requestAction); + } + + [Theory, BitAutoData] + public async Task Get_ShouldReturnOrganizationDomainList_WhenOrgIdIsValid(Guid orgId, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(orgId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(orgId).Returns(new Organization()); + sutProvider.GetDependency() + .GetDomainsByOrganizationId(orgId).Returns(new List + { + new() + { + Id = Guid.NewGuid(), + OrganizationId = orgId, + CreationDate = DateTime.UtcNow.AddDays(-7), + DomainName = "test.com", + Txt = "btw+12342" + } + }); + + var result = await sutProvider.Sut.Get(orgId.ToString()); + + Assert.IsType>(result); + Assert.Equal(orgId.ToString(), result.Data.Select(x => x.OrganizationId).FirstOrDefault()); + } + + [Theory, BitAutoData] + public async Task GetByOrgIdAndId_ShouldThrowUnauthorized_WhenOrgIdCannotManageSso(Guid orgId, Guid id, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(orgId).Returns(false); + + var requestAction = async () => await sutProvider.Sut.Get(orgId.ToString(), id.ToString()); + + await Assert.ThrowsAsync(requestAction); + } + + [Theory, BitAutoData] + public async Task GetByOrgIdAndId_ShouldThrowNotFound_WhenOrganizationDoesNotExist(Guid orgId, Guid id, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(orgId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(orgId).ReturnsNull(); + + var requestAction = async () => await sutProvider.Sut.Get(orgId.ToString(), id.ToString()); + + await Assert.ThrowsAsync(requestAction); + } + + [Theory, BitAutoData] + public async Task GetByOrgIdAndId_ShouldThrowNotFound_WhenOrganizationDomainEntryNotExist(Guid orgId, Guid id, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(orgId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(orgId).Returns(new Organization()); + sutProvider.GetDependency().GetOrganizationDomainById(id).ReturnsNull(); + + var requestAction = async () => await sutProvider.Sut.Get(orgId.ToString(), id.ToString()); + + await Assert.ThrowsAsync(requestAction); + } + + [Theory, BitAutoData] + public async Task Get_ShouldReturnOrganizationDomain_WhenOrgIdAndIdAreValid(Guid orgId, Guid id, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(orgId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(orgId).Returns(new Organization()); + sutProvider.GetDependency().GetOrganizationDomainById(id) + .Returns(new OrganizationDomain + { + Id = Guid.NewGuid(), + OrganizationId = orgId, + CreationDate = DateTime.UtcNow.AddDays(-7), + DomainName = "test.com", + Txt = "btw+12342" + }); + + var result = await sutProvider.Sut.Get(orgId.ToString(), id.ToString()); + + Assert.IsType(result); + Assert.Equal(orgId.ToString(), result.OrganizationId); + } + + [Theory, BitAutoData] + public async Task Post_ShouldThrowUnauthorized_OrgIdCannotManageSso(Guid orgId, OrganizationDomainRequestModel model, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(orgId).Returns(false); + + var requestAction = async () => await sutProvider.Sut.Post(orgId.ToString(), model); + + await Assert.ThrowsAsync(requestAction); + } + + [Theory, BitAutoData] + public async Task Post_ShouldThrowNotFound_WhenOrganizationDoesNotExist(Guid orgId, OrganizationDomainRequestModel model, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(orgId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(orgId).ReturnsNull(); + + var requestAction = async () => await sutProvider.Sut.Post(orgId.ToString(), model); + + await Assert.ThrowsAsync(requestAction); + } + + [Theory, BitAutoData] + public async Task Post_ShouldCreateEntry_WhenRequestIsValid(Guid orgId, OrganizationDomainRequestModel model, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(orgId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(orgId).Returns(new Organization()); + sutProvider.GetDependency().CreateAsync(Arg.Any()) + .Returns(new OrganizationDomain()); + + var result = await sutProvider.Sut.Post(orgId.ToString(), model); + + await sutProvider.GetDependency().ReceivedWithAnyArgs(1) + .CreateAsync(Arg.Any()); + Assert.IsType(result); + } + + [Theory, BitAutoData] + public async Task Verify_ShouldThrowUnauthorized_WhenOrgIdCannotManageSso(Guid orgId, Guid id, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(orgId).Returns(false); + + var requestAction = async () => await sutProvider.Sut.Verify(orgId.ToString(), id.ToString()); + + await Assert.ThrowsAsync(requestAction); + } + + [Theory, BitAutoData] + public async Task Verify_ShouldThrowNotFound_WhenOrganizationDoesNotExist(Guid orgId, Guid id, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(orgId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(orgId).ReturnsNull(); + + var requestAction = async () => await sutProvider.Sut.Verify(orgId.ToString(), id.ToString()); + + await Assert.ThrowsAsync(requestAction); + } + + [Theory, BitAutoData] + public async Task Verify_WhenRequestIsValid(Guid orgId, Guid id, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(orgId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(orgId).Returns(new Organization()); + sutProvider.GetDependency().VerifyOrganizationDomain(id) + .Returns(new OrganizationDomain()); + + var result = await sutProvider.Sut.Verify(orgId.ToString(), id.ToString()); + + await sutProvider.GetDependency().Received(1) + .VerifyOrganizationDomain(id); + Assert.IsType(result); + } + + [Theory, BitAutoData] + public async Task RemoveDomain_ShouldThrowUnauthorized_OrgIdCannotManageSso(Guid orgId, Guid id, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(orgId).Returns(false); + + var requestAction = async () => await sutProvider.Sut.RemoveDomain(orgId.ToString(), id.ToString()); + + await Assert.ThrowsAsync(requestAction); + } + + [Theory, BitAutoData] + public async Task RemoveDomain_ShouldThrowNotFound_WhenOrganizationDoesNotExist(Guid orgId, Guid id, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(orgId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(orgId).ReturnsNull(); + + var requestAction = async () => await sutProvider.Sut.RemoveDomain(orgId.ToString(), id.ToString()); + + await Assert.ThrowsAsync(requestAction); + } + + [Theory, BitAutoData] + public async Task RemoveDomain_WhenRequestIsValid(Guid orgId, Guid id, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(orgId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(orgId).Returns(new Organization()); + + await sutProvider.Sut.RemoveDomain(orgId.ToString(), id.ToString()); + + await sutProvider.GetDependency().Received(1) + .DeleteAsync(id); + } + + [Theory, BitAutoData] + public async Task GetOrgDomainSsoDetails_ShouldThrowNotFound_WhenEmailHasNotClaimedDomain( + OrganizationDomainSsoDetailsRequestModel model, SutProvider sutProvider) + { + sutProvider.GetDependency() + .GetOrganizationDomainSsoDetailsAsync(model.Email).ReturnsNull(); + + var requestAction = async () => await sutProvider.Sut.GetOrgDomainSsoDetails(model); + + await Assert.ThrowsAsync(requestAction); + } + + [Theory, BitAutoData] + public async Task GetOrgDomainSsoDetails_ShouldReturnOrganizationDomainSsoDetails_WhenEmailHasClaimedDomain( + OrganizationDomainSsoDetailsRequestModel model, OrganizationDomainSsoDetailsData ssoDetailsData, SutProvider sutProvider) + { + sutProvider.GetDependency() + .GetOrganizationDomainSsoDetailsAsync(model.Email).Returns(ssoDetailsData); + + var result = await sutProvider.Sut.GetOrgDomainSsoDetails(model); + + Assert.IsType(result); + } +} diff --git a/test/Api.Test/Controllers/OrganizationsControllerTests.cs b/test/Api.Test/Controllers/OrganizationsControllerTests.cs index f056beea80..8755388213 100644 --- a/test/Api.Test/Controllers/OrganizationsControllerTests.cs +++ b/test/Api.Test/Controllers/OrganizationsControllerTests.cs @@ -32,6 +32,7 @@ public class OrganizationsControllerTests : IDisposable private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository; private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery; private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand; + private readonly IOrganizationDomainRepository _organizationDomainRepository; private readonly OrganizationsController _sut; diff --git a/test/Api.Test/packages.lock.json b/test/Api.Test/packages.lock.json index de91a99242..6ca00e3af9 100644 --- a/test/Api.Test/packages.lock.json +++ b/test/Api.Test/packages.lock.json @@ -245,6 +245,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fare": { "type": "Transitive", "resolved": "2.1.1", @@ -3072,6 +3080,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/test/Billing.Test/packages.lock.json b/test/Billing.Test/packages.lock.json index 52ad5d31db..f9b1fef1f8 100644 --- a/test/Billing.Test/packages.lock.json +++ b/test/Billing.Test/packages.lock.json @@ -235,6 +235,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fare": { "type": "Transitive", "resolved": "2.1.1", @@ -3502,6 +3510,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/test/Common/packages.lock.json b/test/Common/packages.lock.json index 93c85ee02a..ca1cee8430 100644 --- a/test/Common/packages.lock.json +++ b/test/Common/packages.lock.json @@ -218,6 +218,14 @@ "System.Xml.XmlDocument": "4.3.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fare": { "type": "Transitive", "resolved": "2.1.1", @@ -2793,6 +2801,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/test/Core.Test/OrganizationFeatures/OrganizationDomains/CreateOrganizationDomainCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationDomains/CreateOrganizationDomainCommandTests.cs new file mode 100644 index 0000000000..70773d38e6 --- /dev/null +++ b/test/Core.Test/OrganizationFeatures/OrganizationDomains/CreateOrganizationDomainCommandTests.cs @@ -0,0 +1,130 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.OrganizationFeatures.OrganizationDomains; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using NSubstitute.ExceptionExtensions; +using NSubstitute.ReturnsExtensions; +using Xunit; + +namespace Bit.Core.Test.OrganizationFeatures.OrganizationDomains; + +[SutProviderCustomize] +public class CreateOrganizationDomainCommandTests +{ + [Theory, BitAutoData] + public async Task CreateAsync_ShouldCreateOrganizationDomainAndLogEvent_WhenDetailsAreValid(OrganizationDomain orgDomain, SutProvider sutProvider) + { + sutProvider.GetDependency() + .GetClaimedDomainsByDomainNameAsync(orgDomain.DomainName) + .Returns(new List()); + sutProvider.GetDependency() + .GetDomainByOrgIdAndDomainNameAsync(orgDomain.OrganizationId, orgDomain.DomainName) + .ReturnsNull(); + sutProvider.GetDependency() + .ResolveAsync(orgDomain.DomainName, orgDomain.Txt) + .Returns(false); + orgDomain.SetNextRunDate(12); + sutProvider.GetDependency() + .CreateAsync(orgDomain) + .Returns(orgDomain); + + + var result = await sutProvider.Sut.CreateAsync(orgDomain); + + Assert.Equal(orgDomain.Id, result.Id); + Assert.Equal(orgDomain.OrganizationId, result.OrganizationId); + Assert.NotNull(result.LastCheckedDate); + Assert.Equal(orgDomain.NextRunDate, result.NextRunDate); + await sutProvider.GetDependency().Received(1) + .LogOrganizationDomainEventAsync(Arg.Any(), EventType.OrganizationDomain_Added); + await sutProvider.GetDependency().Received(1) + .LogOrganizationDomainEventAsync(Arg.Any(), Arg.Is(x => x == EventType.OrganizationDomain_NotVerified)); + } + + [Theory, BitAutoData] + public async Task CreateAsync_ShouldThrowConflictException_WhenDomainIsClaimed(OrganizationDomain orgDomain, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .GetClaimedDomainsByDomainNameAsync(orgDomain.DomainName) + .Returns(new List() + { + orgDomain + }); + + var requestAction = async () => await sutProvider.Sut.CreateAsync(orgDomain); + + var exception = await Assert.ThrowsAsync(requestAction); + Assert.Contains("The domain is not available to be claimed.", exception.Message); + } + + [Theory, BitAutoData] + public async Task CreateAsync_ShouldThrowConflictException_WhenEntryIsDuplicatedForOrganization(OrganizationDomain orgDomain, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .GetClaimedDomainsByDomainNameAsync(orgDomain.DomainName) + .Returns(new List()); + sutProvider.GetDependency() + .GetDomainByOrgIdAndDomainNameAsync(orgDomain.OrganizationId, orgDomain.DomainName) + .Returns(orgDomain); + + var requestAction = async () => await sutProvider.Sut.CreateAsync(orgDomain); + + var exception = await Assert.ThrowsAsync(requestAction); + Assert.Contains("A domain already exists for this organization.", exception.Message); + } + + [Theory, BitAutoData] + public async Task CreateAsync_ShouldNotSetVerifiedDate_WhenDomainCannotBeResolved(OrganizationDomain orgDomain, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .GetClaimedDomainsByDomainNameAsync(orgDomain.DomainName) + .Returns(new List()); + sutProvider.GetDependency() + .GetDomainByOrgIdAndDomainNameAsync(orgDomain.OrganizationId, orgDomain.DomainName) + .ReturnsNull(); + sutProvider.GetDependency() + .ResolveAsync(orgDomain.DomainName, orgDomain.Txt) + .Throws(new DnsQueryException("")); + sutProvider.GetDependency() + .CreateAsync(orgDomain) + .Returns(orgDomain); + + await sutProvider.Sut.CreateAsync(orgDomain); + + Assert.Null(orgDomain.VerifiedDate); + } + + [Theory, BitAutoData] + public async Task CreateAsync_ShouldSetVerifiedDateAndLogEvent_WhenDomainIsResolved(OrganizationDomain orgDomain, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .GetClaimedDomainsByDomainNameAsync(orgDomain.DomainName) + .Returns(new List()); + sutProvider.GetDependency() + .GetDomainByOrgIdAndDomainNameAsync(orgDomain.OrganizationId, orgDomain.DomainName) + .ReturnsNull(); + sutProvider.GetDependency() + .ResolveAsync(orgDomain.DomainName, orgDomain.Txt) + .Returns(true); + sutProvider.GetDependency() + .CreateAsync(orgDomain) + .Returns(orgDomain); + + var result = await sutProvider.Sut.CreateAsync(orgDomain); + + Assert.NotNull(result.VerifiedDate); + await sutProvider.GetDependency().Received(1) + .LogOrganizationDomainEventAsync(Arg.Any(), EventType.OrganizationDomain_Added); + await sutProvider.GetDependency().Received(1) + .LogOrganizationDomainEventAsync(Arg.Any(), Arg.Is(x => x == EventType.OrganizationDomain_Verified)); + } +} diff --git a/test/Core.Test/OrganizationFeatures/OrganizationDomains/DeleteOrganizationDomainCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationDomains/DeleteOrganizationDomainCommandTests.cs new file mode 100644 index 0000000000..b9201d35af --- /dev/null +++ b/test/Core.Test/OrganizationFeatures/OrganizationDomains/DeleteOrganizationDomainCommandTests.cs @@ -0,0 +1,47 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.OrganizationFeatures.OrganizationDomains; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using NSubstitute.ReturnsExtensions; +using Xunit; + +namespace Bit.Core.Test.OrganizationFeatures.OrganizationDomains; + +[SutProviderCustomize] +public class DeleteOrganizationDomainCommandTests +{ + [Theory, BitAutoData] + public async Task DeleteAsync_ShouldThrowNotFoundException_WhenIdDoesNotExist(Guid id, + SutProvider sutProvider) + { + sutProvider.GetDependency().GetByIdAsync(id).ReturnsNull(); + + var requestAction = async () => await sutProvider.Sut.DeleteAsync(id); + + await Assert.ThrowsAsync(requestAction); + } + + [Theory, BitAutoData] + public async Task DeleteAsync_Success(Guid id, SutProvider sutProvider) + { + var expected = new OrganizationDomain + { + Id = id, + OrganizationId = Guid.NewGuid(), + DomainName = "Test Domain", + Txt = "btw+test18383838383" + }; + sutProvider.GetDependency().GetByIdAsync(id).Returns(expected); + + await sutProvider.Sut.DeleteAsync(id); + + await sutProvider.GetDependency().Received(1).DeleteAsync(expected); + await sutProvider.GetDependency().Received(1) + .LogOrganizationDomainEventAsync(Arg.Any(), EventType.OrganizationDomain_Removed); + } +} diff --git a/test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdQueryTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdQueryTests.cs new file mode 100644 index 0000000000..0cb77d243a --- /dev/null +++ b/test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdQueryTests.cs @@ -0,0 +1,22 @@ +using Bit.Core.OrganizationFeatures.OrganizationDomains; +using Bit.Core.Repositories; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.OrganizationFeatures.OrganizationDomains; + +[SutProviderCustomize] +public class GetOrganizationDomainByIdQueryTests +{ + [Theory, BitAutoData] + public async Task GetOrganizationDomainById_CallsGetByIdAsync(Guid id, + SutProvider sutProvider) + { + await sutProvider.Sut.GetOrganizationDomainById(id); + + await sutProvider.GetDependency().Received(1) + .GetByIdAsync(id); + } +} diff --git a/test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByOrganizationIdQueryTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByOrganizationIdQueryTests.cs new file mode 100644 index 0000000000..964a3a76d4 --- /dev/null +++ b/test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByOrganizationIdQueryTests.cs @@ -0,0 +1,22 @@ +using Bit.Core.OrganizationFeatures.OrganizationDomains; +using Bit.Core.Repositories; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.OrganizationFeatures.OrganizationDomains; + +[SutProviderCustomize] +public class GetOrganizationDomainByOrganizationIdQueryTests +{ + [Theory, BitAutoData] + public async Task GetDomainsByOrganizationId_CallsGetDomainsByOrganizationIdAsync(Guid orgId, + SutProvider sutProvider) + { + await sutProvider.Sut.GetDomainsByOrganizationId(orgId); + + await sutProvider.GetDependency().Received(1) + .GetDomainsByOrganizationIdAsync(orgId); + } +} diff --git a/test/Core.Test/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommandTests.cs new file mode 100644 index 0000000000..54783982ad --- /dev/null +++ b/test/Core.Test/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommandTests.cs @@ -0,0 +1,135 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.OrganizationFeatures.OrganizationDomains; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using NSubstitute.ReceivedExtensions; +using NSubstitute.ReturnsExtensions; +using Xunit; + +namespace Bit.Core.Test.OrganizationFeatures.OrganizationDomains; + +[SutProviderCustomize] +public class VerifyOrganizationDomainCommandTests +{ + [Theory, BitAutoData] + public async Task VerifyOrganizationDomain_ShouldThrowNotFound_WhenDomainDoesNotExist(Guid id, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .GetByIdAsync(id) + .ReturnsNull(); + + var requestAction = async () => await sutProvider.Sut.VerifyOrganizationDomain(id); + + await Assert.ThrowsAsync(requestAction); + } + + [Theory, BitAutoData] + public async Task VerifyOrganizationDomain_ShouldThrowConflict_WhenDomainHasBeenClaimed(Guid id, + SutProvider sutProvider) + { + var expected = new OrganizationDomain + { + Id = id, + OrganizationId = Guid.NewGuid(), + DomainName = "Test Domain", + Txt = "btw+test18383838383" + }; + expected.SetVerifiedDate(); + sutProvider.GetDependency() + .GetByIdAsync(id) + .Returns(expected); + + var requestAction = async () => await sutProvider.Sut.VerifyOrganizationDomain(id); + + var exception = await Assert.ThrowsAsync(requestAction); + Assert.Contains("Domain has already been verified.", exception.Message); + } + + [Theory, BitAutoData] + public async Task VerifyOrganizationDomain_ShouldThrowConflict_WhenDomainHasBeenClaimedByAnotherOrganization(Guid id, + SutProvider sutProvider) + { + var expected = new OrganizationDomain + { + Id = id, + OrganizationId = Guid.NewGuid(), + DomainName = "Test Domain", + Txt = "btw+test18383838383" + }; + sutProvider.GetDependency() + .GetByIdAsync(id) + .Returns(expected); + sutProvider.GetDependency() + .GetClaimedDomainsByDomainNameAsync(expected.DomainName) + .Returns(new List { expected }); + + var requestAction = async () => await sutProvider.Sut.VerifyOrganizationDomain(id); + + var exception = await Assert.ThrowsAsync(requestAction); + Assert.Contains("The domain is not available to be claimed.", exception.Message); + } + + [Theory, BitAutoData] + public async Task VerifyOrganizationDomain_ShouldVerifyDomainUpdateAndLogEvent_WhenTxtRecordExists(Guid id, + SutProvider sutProvider) + { + var expected = new OrganizationDomain + { + Id = id, + OrganizationId = Guid.NewGuid(), + DomainName = "Test Domain", + Txt = "btw+test18383838383" + }; + sutProvider.GetDependency() + .GetByIdAsync(id) + .Returns(expected); + sutProvider.GetDependency() + .GetClaimedDomainsByDomainNameAsync(expected.DomainName) + .Returns(new List()); + sutProvider.GetDependency() + .ResolveAsync(expected.DomainName, Arg.Any()) + .Returns(true); + + var result = await sutProvider.Sut.VerifyOrganizationDomain(id); + + Assert.NotNull(result.VerifiedDate); + await sutProvider.GetDependency().Received(1) + .ReplaceAsync(Arg.Any()); + await sutProvider.GetDependency().Received(1) + .LogOrganizationDomainEventAsync(Arg.Any(), EventType.OrganizationDomain_Verified); + } + + [Theory, BitAutoData] + public async Task VerifyOrganizationDomain_ShouldNotSetVerifiedDate_WhenTxtRecordDoesNotExist(Guid id, + SutProvider sutProvider) + { + var expected = new OrganizationDomain + { + Id = id, + OrganizationId = Guid.NewGuid(), + DomainName = "Test Domain", + Txt = "btw+test18383838383" + }; + sutProvider.GetDependency() + .GetByIdAsync(id) + .Returns(expected); + sutProvider.GetDependency() + .GetClaimedDomainsByDomainNameAsync(expected.DomainName) + .Returns(new List()); + sutProvider.GetDependency() + .ResolveAsync(expected.DomainName, Arg.Any()) + .Returns(false); + + var result = await sutProvider.Sut.VerifyOrganizationDomain(id); + + Assert.Null(result.VerifiedDate); + await sutProvider.GetDependency().Received(1) + .LogOrganizationDomainEventAsync(Arg.Any(), EventType.OrganizationDomain_NotVerified); + } +} diff --git a/test/Core.Test/Services/EventServiceTests.cs b/test/Core.Test/Services/EventServiceTests.cs index cd3498f306..8454f05f5f 100644 --- a/test/Core.Test/Services/EventServiceTests.cs +++ b/test/Core.Test/Services/EventServiceTests.cs @@ -71,19 +71,26 @@ public class EventServiceTests await sutProvider.Sut.LogGroupEventAsync(group, eventType, eventSystemUser, date); + var eventMessage = new EventMessage() + { + IpAddress = ipAddress, + DeviceType = deviceType, + OrganizationId = group.OrganizationId, + GroupId = group.Id, + Type = eventType, + ActingUserId = actingUserId, + ProviderId = providerId, + Date = date, + SystemUser = eventSystemUser + }; + + if (eventSystemUser is EventSystemUser.SCIM) + { + eventMessage.DeviceType = DeviceType.Server; + } + var expected = new List() { - new EventMessage() - { - IpAddress = ipAddress, - DeviceType = deviceType, - OrganizationId = group.OrganizationId, - GroupId = group.Id, - Type = eventType, - ActingUserId = actingUserId, - ProviderId = providerId, - Date = date, - SystemUser = eventSystemUser - } + eventMessage }; await sutProvider.GetDependency().Received(1).CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(expected, new[] { "IdempotencyId" }))); @@ -143,7 +150,7 @@ public class EventServiceTests [Theory, BitAutoData] public async Task LogOrganizationUserEvent_WithEventSystemUser_LogsRequiredInfo(OrganizationUser orgUser, EventType eventType, EventSystemUser eventSystemUser, DateTime date, - Guid actingUserId, Guid providerId, string ipAddress, DeviceType deviceType, SutProvider sutProvider) + Guid actingUserId, Guid providerId, string ipAddress, SutProvider sutProvider) { var orgAbilities = new Dictionary() { @@ -153,7 +160,6 @@ public class EventServiceTests sutProvider.GetDependency().UserId.Returns(actingUserId); sutProvider.GetDependency().IpAddress.Returns(ipAddress); sutProvider.GetDependency().ProviderIdForOrg(Arg.Any()).Returns(providerId); - sutProvider.GetDependency().DeviceType.Returns(deviceType); await sutProvider.Sut.LogOrganizationUserEventAsync(orgUser, eventType, eventSystemUser, date); @@ -161,7 +167,7 @@ public class EventServiceTests new EventMessage() { IpAddress = ipAddress, - DeviceType = deviceType, + DeviceType = DeviceType.Server, OrganizationId = orgUser.OrganizationId, UserId = orgUser.UserId, OrganizationUserId = orgUser.Id, diff --git a/test/Core.Test/Services/OrganizationDomainServiceTests.cs b/test/Core.Test/Services/OrganizationDomainServiceTests.cs new file mode 100644 index 0000000000..b6f299b3a6 --- /dev/null +++ b/test/Core.Test/Services/OrganizationDomainServiceTests.cs @@ -0,0 +1,83 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.Services; + +[SutProviderCustomize] +public class OrganizationDomainServiceTests +{ + + [Theory, BitAutoData] + public async Task ValidateOrganizationsDomainAsync_CallsDnsResolverServiceAndReplace(SutProvider sutProvider) + { + var domains = new List + { + new() + { + Id = Guid.NewGuid(), + OrganizationId = Guid.NewGuid(), + CreationDate = DateTime.UtcNow, + DomainName = "test.com", + Txt = "btw+12345", + }, + new() + { + Id = Guid.NewGuid(), + OrganizationId = Guid.NewGuid(), + CreationDate = DateTime.UtcNow, + DomainName = "test2.com", + Txt = "btw+6789" + } + }; + sutProvider.GetDependency().GetManyByNextRunDateAsync(default) + .ReturnsForAnyArgs(domains); + + await sutProvider.Sut.ValidateOrganizationsDomainAsync(); + + await sutProvider.GetDependency().ReceivedWithAnyArgs(2) + .ResolveAsync(default, default); + await sutProvider.GetDependency().ReceivedWithAnyArgs(2) + .ReplaceAsync(default); + await sutProvider.GetDependency().ReceivedWithAnyArgs(2) + .LogOrganizationDomainEventAsync(default, EventType.OrganizationDomain_NotVerified, + EventSystemUser.DomainVerification); + } + + [Theory, BitAutoData] + public async Task OrganizationDomainMaintenanceAsync_CallsDeleteExpiredAsync_WhenExpiredDomainsExist( + SutProvider sutProvider) + { + var expiredDomains = new List + { + new() + { + Id = Guid.NewGuid(), + OrganizationId = Guid.NewGuid(), + CreationDate = DateTime.UtcNow, + DomainName = "test.com", + Txt = "btw+12345", + }, + new() + { + Id = Guid.NewGuid(), + OrganizationId = Guid.NewGuid(), + CreationDate = DateTime.UtcNow, + DomainName = "test2.com", + Txt = "btw+6789" + } + }; + sutProvider.GetDependency().GetExpiredOrganizationDomainsAsync() + .Returns(expiredDomains); + + await sutProvider.Sut.OrganizationDomainMaintenanceAsync(); + + await sutProvider.GetDependency().ReceivedWithAnyArgs(1) + .DeleteExpiredAsync(7); + } +} diff --git a/test/Core.Test/packages.lock.json b/test/Core.Test/packages.lock.json index 0f54629a53..f955a523a2 100644 --- a/test/Core.Test/packages.lock.json +++ b/test/Core.Test/packages.lock.json @@ -234,6 +234,14 @@ "System.Xml.XmlDocument": "4.3.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fare": { "type": "Transitive", "resolved": "2.1.1", @@ -2821,6 +2829,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/test/Icons.Test/packages.lock.json b/test/Icons.Test/packages.lock.json index 3222145ccc..9748de731c 100644 --- a/test/Icons.Test/packages.lock.json +++ b/test/Icons.Test/packages.lock.json @@ -216,6 +216,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2915,6 +2923,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/test/Identity.IntegrationTest/packages.lock.json b/test/Identity.IntegrationTest/packages.lock.json index 520d2901c2..e1cbb115a1 100644 --- a/test/Identity.IntegrationTest/packages.lock.json +++ b/test/Identity.IntegrationTest/packages.lock.json @@ -246,6 +246,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fare": { "type": "Transitive", "resolved": "2.1.1", @@ -3140,6 +3148,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/test/Identity.Test/packages.lock.json b/test/Identity.Test/packages.lock.json index 223f295510..40b0b4f5d0 100644 --- a/test/Identity.Test/packages.lock.json +++ b/test/Identity.Test/packages.lock.json @@ -235,6 +235,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fare": { "type": "Transitive", "resolved": "2.1.1", @@ -3007,6 +3015,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/test/Infrastructure.EFIntegration.Test/packages.lock.json b/test/Infrastructure.EFIntegration.Test/packages.lock.json index 0194056cde..6c4e327eba 100644 --- a/test/Infrastructure.EFIntegration.Test/packages.lock.json +++ b/test/Infrastructure.EFIntegration.Test/packages.lock.json @@ -246,6 +246,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fare": { "type": "Transitive", "resolved": "2.1.1", @@ -2997,6 +3005,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/test/Infrastructure.IntegrationTest/packages.lock.json b/test/Infrastructure.IntegrationTest/packages.lock.json index 876a4117e4..87b67248d9 100644 --- a/test/Infrastructure.IntegrationTest/packages.lock.json +++ b/test/Infrastructure.IntegrationTest/packages.lock.json @@ -220,6 +220,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2845,6 +2853,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/test/IntegrationTestCommon/packages.lock.json b/test/IntegrationTestCommon/packages.lock.json index e65cbeb55d..df6bebf4ed 100644 --- a/test/IntegrationTestCommon/packages.lock.json +++ b/test/IntegrationTestCommon/packages.lock.json @@ -222,6 +222,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fare": { "type": "Transitive", "resolved": "2.1.1", @@ -3126,6 +3134,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/util/Migrator/DbScripts/2022-11-03_00_OrganizationDomainInit.sql b/util/Migrator/DbScripts/2022-11-03_00_OrganizationDomainInit.sql new file mode 100644 index 0000000000..94c04c3217 --- /dev/null +++ b/util/Migrator/DbScripts/2022-11-03_00_OrganizationDomainInit.sql @@ -0,0 +1,285 @@ +-- Create Organization Domain table +IF OBJECT_ID('[dbo].[OrganizationDomain]') IS NOT NULL +BEGIN + DROP TABLE [dbo].[OrganizationDomain] +END +GO + +IF OBJECT_ID('[dbo].[OrganizationDomain]') IS NULL +BEGIN +CREATE TABLE [dbo].[OrganizationDomain] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [OrganizationId] UNIQUEIDENTIFIER NOT NULL, + [Txt] VARCHAR(MAX) NOT NULL, + [DomainName] NVARCHAR(255) NOT NULL, + [CreationDate] DATETIME2(7) NOT NULL, + [VerifiedDate] DATETIME2(7) NULL, + [LastCheckedDate] DATETIME2(7) NULL, + [NextRunDate] DATETIME2(7) NOT NULL, + [JobRunCount] TINYINT NOT NULL + CONSTRAINT [PK_OrganizationDomain] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_OrganzationDomain_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) +) +END +GO + +-- Create View +CREATE OR ALTER VIEW [dbo].[OrganizationDomainView] +AS +SELECT + * +FROM + [dbo].[OrganizationDomain] +GO + +-- Organization Domain CRUD SPs +-- Create +CREATE OR ALTER PROCEDURE [dbo].[OrganizationDomain_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Txt VARCHAR(MAX), + @DomainName NVARCHAR(255), + @CreationDate DATETIME2(7), + @VerifiedDate DATETIME2(7), + @LastCheckedDate DATETIME2(7), + @NextRunDate DATETIME2(7), + @JobRunCount TINYINT +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[OrganizationDomain] + ( + [Id], + [OrganizationId], + [Txt], + [DomainName], + [CreationDate], + [VerifiedDate], + [LastCheckedDate], + [NextRunDate], + [JobRunCount] + ) + VALUES + ( + @Id, + @OrganizationId, + @Txt, + @DomainName, + @CreationDate, + @VerifiedDate, + @LastCheckedDate, + @NextRunDate, + @JobRunCount + ) +END +GO + +--Update +CREATE OR ALTER PROCEDURE [dbo].[OrganizationDomain_Update] + @Id UNIQUEIDENTIFIER OUTPUT, + @OrganizationId UNIQUEIDENTIFIER, + @Txt VARCHAR(MAX), + @DomainName NVARCHAR(255), + @CreationDate DATETIME2(7), + @VerifiedDate DATETIME2(7), + @LastCheckedDate DATETIME2(7), + @NextRunDate DATETIME2(7), + @JobRunCount TINYINT +AS +BEGIN + SET NOCOUNT ON + +UPDATE + [dbo].[OrganizationDomain] +SET + [OrganizationId] = @OrganizationId, + [Txt] = @Txt, + [DomainName] = @DomainName, + [CreationDate] = @CreationDate, + [VerifiedDate] = @VerifiedDate, + [LastCheckedDate] = @LastCheckedDate, + [NextRunDate] = @NextRunDate, + [JobRunCount] = @JobRunCount +WHERE + [Id] = @Id +END +GO + +--Read +CREATE OR ALTER PROCEDURE [dbo].[OrganizationDomain_ReadById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + +SELECT + * +FROM + [dbo].[OrganizationDomain] +WHERE + [Id] = @Id +END +GO + +--Delete +CREATE OR ALTER PROCEDURE [dbo].[OrganizationDomain_DeleteById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + +DELETE +FROM + [dbo].[OrganizationDomain] +WHERE + [Id] = @Id +END +GO + +-- SP to get claimed domain by domain name +CREATE OR ALTER PROCEDURE [dbo].[OrganizationDomain_ReadByClaimedDomain] + @DomainName NVARCHAR(255) +AS +BEGIN + SET NOCOUNT ON + +SELECT + * +FROM + [dbo].[OrganizationDomain] +WHERE + [DomainName] = @DomainName + AND + [VerifiedDate] IS NOT NULL +END +GO + +-- SP to get domains by OrganizationId +CREATE OR ALTER PROCEDURE [dbo].[OrganizationDomain_ReadByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + +SELECT + * +FROM + [dbo].[OrganizationDomain] +WHERE + [OrganizationId] = @OrganizationId +END +GO + +--SP to get domain by organizationId and domainName +CREATE OR ALTER PROCEDURE [dbo].[OrganizationDomain_ReadDomainByOrgIdAndDomainName] + @OrganizationId UNIQUEIDENTIFIER, + @DomainName NVARCHAR(255) +AS +BEGIN + SET NOCOUNT ON + +SELECT + * +FROM + [dbo].[OrganizationDomain] +WHERE + [OrganizationId] = @OrganizationId + AND + [DomainName] = @DomainName +END +GO + +--SP Read by nextRunDate +CREATE OR ALTER PROCEDURE [dbo].[OrganizationDomain_ReadByNextRunDate] + @Date DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + +SELECT + * +FROM + [dbo].[OrganizationDomain] +WHERE [VerifiedDate] IS NULL + AND [JobRunCount] != 3 + AND DATEPART(year, [NextRunDate]) = DATEPART(year, @Date) + AND DATEPART(month, [NextRunDate]) = DATEPART(month, @Date) + AND DATEPART(day, [NextRunDate]) = DATEPART(day, @Date) + AND DATEPART(hour, [NextRunDate]) = DATEPART(hour, @Date) +UNION +SELECT + * +FROM + [dbo].[OrganizationDomain] +WHERE DATEDIFF(hour, [NextRunDate], @Date) > 36 + AND [VerifiedDate] IS NULL + AND [JobRunCount] != 3 +END +GO + +-- SP to get all domains that have not been verified within 72 hours +CREATE OR ALTER PROCEDURE [dbo].[OrganizationDomain_ReadIfExpired] +AS +BEGIN + SET NOCOUNT OFF + +SELECT + * +FROM + [dbo].[OrganizationDomain] +WHERE + DATEDIFF(DAY, [CreationDate], GETUTCDATE()) >= 4 --Get domains that have not been verified after 3 days (72 hours) + AND + [VerifiedDate] IS NULL +END +GO + +-- SP to delete domains that have been left unverified for 7 days +CREATE OR ALTER PROCEDURE [dbo].[OrganizationDomain_DeleteIfExpired] + @ExpirationPeriod TINYINT +AS +BEGIN + SET NOCOUNT OFF + +DELETE FROM [dbo].[OrganizationDomain] +WHERE DATEDIFF(DAY, [LastCheckedDate], GETUTCDATE()) >= @ExpirationPeriod + AND [VerifiedDate] IS NULL +END +GO + +-- SP to get Organization SSO Provider details by Email +CREATE OR ALTER PROCEDURE [dbo].[OrganizationDomainSsoDetails_ReadByEmail] + @Email NVARCHAR(256) +AS +BEGIN + SET NOCOUNT ON + + DECLARE @Domain NVARCHAR(256) + +SELECT @Domain = SUBSTRING(@Email, CHARINDEX( '@', @Email) + 1, LEN(@Email)) + +SELECT + O.Id AS OrganizationId, + O.[Name] AS OrganizationName, + O.UseSso AS SsoAvailable, + P.Enabled AS SsoRequired, + O.Identifier AS OrganizationIdentifier, + OD.VerifiedDate, + P.[Type] AS PolicyType, + OD.DomainName +FROM + [dbo].[OrganizationView] O + INNER JOIN [dbo].[OrganizationDomainView] OD +ON O.Id = OD.OrganizationId + -- use left join instead of inner join so that results + -- come back even if org doesn't have a policy yet for + -- requiring SSO + LEFT JOIN [dbo].[PolicyView] P + ON O.Id = P.OrganizationId +WHERE OD.DomainName = @Domain + AND O.Enabled = 1 + -- Handle null results + AND (P.Id is NULL OR (P.Id IS NOT NULL AND P.[Type] = 4)) -- SSO Type +END +GO \ No newline at end of file diff --git a/util/Migrator/DbScripts/2022-12-08_00_EventsDomainName.sql b/util/Migrator/DbScripts/2022-12-08_00_EventsDomainName.sql new file mode 100644 index 0000000000..c04bf06916 --- /dev/null +++ b/util/Migrator/DbScripts/2022-12-08_00_EventsDomainName.sql @@ -0,0 +1,95 @@ +--Add column DomainName to Event table +IF COL_LENGTH('[dbo].[Event]', 'DomainName') IS NULL + BEGIN + ALTER TABLE + [dbo].[Event] + ADD + [DomainName] VARCHAR(256) NULL; + END +GO + +-- Recreate EventView +IF OBJECT_ID('[dbo].[EventView]') IS NOT NULL +BEGIN + DROP VIEW [dbo].[EventView] +END +GO + +CREATE VIEW [dbo].[EventView] +AS +SELECT + * +FROM + [dbo].[Event] +GO + +CREATE OR ALTER PROCEDURE [dbo].[Event_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @Type INT, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @InstallationId UNIQUEIDENTIFIER, + @ProviderId UNIQUEIDENTIFIER, + @CipherId UNIQUEIDENTIFIER, + @CollectionId UNIQUEIDENTIFIER, + @PolicyId UNIQUEIDENTIFIER, + @GroupId UNIQUEIDENTIFIER, + @OrganizationUserId UNIQUEIDENTIFIER, + @ProviderUserId UNIQUEIDENTIFIER, + @ProviderOrganizationId UNIQUEIDENTIFIER = null, + @ActingUserId UNIQUEIDENTIFIER, + @DeviceType SMALLINT, + @IpAddress VARCHAR(50), + @Date DATETIME2(7), + @SystemUser TINYINT = null, + @DomainName VARCHAR(256) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Event] + ( + [Id], + [Type], + [UserId], + [OrganizationId], + [InstallationId], + [ProviderId], + [CipherId], + [CollectionId], + [PolicyId], + [GroupId], + [OrganizationUserId], + [ProviderUserId], + [ProviderOrganizationId], + [ActingUserId], + [DeviceType], + [IpAddress], + [Date], + [SystemUser], + [DomainName] + ) + VALUES + ( + @Id, + @Type, + @UserId, + @OrganizationId, + @InstallationId, + @ProviderId, + @CipherId, + @CollectionId, + @PolicyId, + @GroupId, + @OrganizationUserId, + @ProviderUserId, + @ProviderOrganizationId, + @ActingUserId, + @DeviceType, + @IpAddress, + @Date, + @SystemUser, + @DomainName + ) +END +GO \ No newline at end of file diff --git a/util/Migrator/DbScripts/2023_01_18_00_FixOrganizationDeleteOrgDomain.sql b/util/Migrator/DbScripts/2023_01_18_00_FixOrganizationDeleteOrgDomain.sql new file mode 100644 index 0000000000..eeea4ea7cc --- /dev/null +++ b/util/Migrator/DbScripts/2023_01_18_00_FixOrganizationDeleteOrgDomain.sql @@ -0,0 +1,114 @@ +-- Create OrganizationDomain delete by OrganizationId +CREATE OR ALTER PROCEDURE [dbo].[OrganizationDomain_OrganizationDeleted] + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + +DELETE +FROM + [dbo].[OrganizationDomain] +WHERE + [OrganizationId] = @OrganizationId +END +GO + +-- Update Organization Delete By Id to include OrganizationDomain_OrganizationDeleted +CREATE OR ALTER PROCEDURE [dbo].[Organization_DeleteById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @Id + + DECLARE @BatchSize INT = 100 + WHILE @BatchSize > 0 +BEGIN +BEGIN TRANSACTION Organization_DeleteById_Ciphers + + DELETE TOP(@BatchSize) + FROM + [dbo].[Cipher] + WHERE + [UserId] IS NULL + AND [OrganizationId] = @Id + + SET @BatchSize = @@ROWCOUNT + + COMMIT TRANSACTION Organization_DeleteById_Ciphers +END + +BEGIN TRANSACTION Organization_DeleteById + +DELETE +FROM + [dbo].[SsoUser] +WHERE + [OrganizationId] = @Id + +DELETE +FROM + [dbo].[SsoConfig] +WHERE + [OrganizationId] = @Id + +DELETE CU + FROM + [dbo].[CollectionUser] CU + INNER JOIN + [dbo].[OrganizationUser] OU ON [CU].[OrganizationUserId] = [OU].[Id] + WHERE + [OU].[OrganizationId] = @Id + +DELETE +FROM + [dbo].[OrganizationUser] +WHERE + [OrganizationId] = @Id + +DELETE +FROM + [dbo].[ProviderOrganization] +WHERE + [OrganizationId] = @Id + + EXEC [dbo].[OrganizationApiKey_OrganizationDeleted] @Id + EXEC [dbo].[OrganizationConnection_OrganizationDeleted] @Id + EXEC [dbo].[OrganizationSponsorship_OrganizationDeleted] @Id + EXEC [dbo].[OrganizationDomain_OrganizationDeleted] @Id + +DELETE +FROM + [dbo].[Project] +WHERE + [OrganizationId] = @Id + +DELETE +FROM + [dbo].[Secret] +WHERE + [OrganizationId] = @Id + +DELETE AK + FROM + [dbo].[ApiKey] AK + INNER JOIN + [dbo].[ServiceAccount] SA ON [AK].[ServiceAccountId] = [SA].[Id] + WHERE + [SA].[OrganizationId] = @Id + +DELETE +FROM + [dbo].[ServiceAccount] +WHERE + [OrganizationId] = @Id + +DELETE +FROM + [dbo].[Organization] +WHERE + [Id] = @Id + + COMMIT TRANSACTION Organization_DeleteById +END \ No newline at end of file diff --git a/util/Migrator/packages.lock.json b/util/Migrator/packages.lock.json index 02ea2191ba..a55fa2a08c 100644 --- a/util/Migrator/packages.lock.json +++ b/util/Migrator/packages.lock.json @@ -159,6 +159,14 @@ "System.Diagnostics.TraceSource": "4.3.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2613,6 +2621,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/util/MySqlMigrations/HelperScripts/2022-11-03_00_OrganizationDomainClaim.sql b/util/MySqlMigrations/HelperScripts/2022-11-03_00_OrganizationDomainClaim.sql new file mode 100644 index 0000000000..49279dac92 --- /dev/null +++ b/util/MySqlMigrations/HelperScripts/2022-11-03_00_OrganizationDomainClaim.sql @@ -0,0 +1,21 @@ +START TRANSACTION; + +CREATE TABLE `OrganizationDomain` ( + `Id` char(36) COLLATE ascii_general_ci NOT NULL, + `OrganizationId` char(36) COLLATE ascii_general_ci NOT NULL, + `Txt` longtext CHARACTER SET utf8mb4 NULL, + `DomainName` varchar(255) CHARACTER SET utf8mb4 NULL, + `CreationDate` datetime(6) NOT NULL, + `VerifiedDate` datetime(6) NULL, + `NextRunDate` datetime(6) NOT NULL, + `NextRunCount` int NOT NULL, + CONSTRAINT `PK_OrganizationDomain` PRIMARY KEY (`Id`), + CONSTRAINT `FK_OrganizationDomain_Organization_OrganizationId` FOREIGN KEY (`OrganizationId`) REFERENCES `Organization` (`Id`) ON DELETE CASCADE +) CHARACTER SET=utf8mb4; + +CREATE INDEX `IX_OrganizationDomain_OrganizationId` ON `OrganizationDomain` (`OrganizationId`); + +INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) +VALUES ('20221108015516_OrganizationDomainClaim', '6.0.4'); + +COMMIT; \ No newline at end of file diff --git a/util/MySqlMigrations/HelperScripts/2022-11-28_00_OrganizationDomainClaimRenameNextRunCount.sql b/util/MySqlMigrations/HelperScripts/2022-11-28_00_OrganizationDomainClaimRenameNextRunCount.sql new file mode 100644 index 0000000000..49d35fc005 --- /dev/null +++ b/util/MySqlMigrations/HelperScripts/2022-11-28_00_OrganizationDomainClaimRenameNextRunCount.sql @@ -0,0 +1,8 @@ +START TRANSACTION; + +ALTER TABLE `OrganizationDomain` RENAME COLUMN `NextRunCount` TO `JobRunCount`; + +INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) +VALUES ('20221129004644_OrganizationDomainClaimRenameNextRunCount', '6.0.4'); + +COMMIT; \ No newline at end of file diff --git a/util/MySqlMigrations/HelperScripts/2022-12-08_00_EventsDomainName.sql b/util/MySqlMigrations/HelperScripts/2022-12-08_00_EventsDomainName.sql new file mode 100644 index 0000000000..bba4fe72fb --- /dev/null +++ b/util/MySqlMigrations/HelperScripts/2022-12-08_00_EventsDomainName.sql @@ -0,0 +1,9 @@ +START TRANSACTION; + +ALTER TABLE `Event` ADD `DomainName` longtext CHARACTER SET utf8mb4 NULL; + +INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) +VALUES ('20221209015017_EventsDomainName', '6.0.4'); + +COMMIT; + diff --git a/util/MySqlMigrations/HelperScripts/2022-12-09_00_OrganizationDomainLastCheckedDate.sql b/util/MySqlMigrations/HelperScripts/2022-12-09_00_OrganizationDomainLastCheckedDate.sql new file mode 100644 index 0000000000..5204026f97 --- /dev/null +++ b/util/MySqlMigrations/HelperScripts/2022-12-09_00_OrganizationDomainLastCheckedDate.sql @@ -0,0 +1,9 @@ +START TRANSACTION; + +ALTER TABLE `OrganizationDomain` ADD `LastCheckedDate` datetime(6) NULL; + +INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) +VALUES ('20221209192355_OrganizationDomainLastCheckedDate', '6.0.4'); + +COMMIT; + diff --git a/util/MySqlMigrations/Migrations/20221114202829_OrganizationDomainClaim.Designer.cs b/util/MySqlMigrations/Migrations/20221114202829_OrganizationDomainClaim.Designer.cs new file mode 100644 index 0000000000..9df3c72b4e --- /dev/null +++ b/util/MySqlMigrations/Migrations/20221114202829_OrganizationDomainClaim.Designer.cs @@ -0,0 +1,1722 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20221114202829_OrganizationDomainClaim")] + partial class OrganizationDomainClaim + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestFingerprint") + .HasColumnType("longtext"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("NextRunCount") + .HasColumnType("int"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UnknownDeviceVerificationEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20221114202829_OrganizationDomainClaim.cs b/util/MySqlMigrations/Migrations/20221114202829_OrganizationDomainClaim.cs new file mode 100644 index 0000000000..93c5c522b6 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20221114202829_OrganizationDomainClaim.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +public partial class OrganizationDomainClaim : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "OrganizationDomain", + columns: table => new + { + Id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + OrganizationId = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), + Txt = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + DomainName = table.Column(type: "varchar(255)", maxLength: 255, nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + CreationDate = table.Column(type: "datetime(6)", nullable: false), + VerifiedDate = table.Column(type: "datetime(6)", nullable: true), + NextRunDate = table.Column(type: "datetime(6)", nullable: false), + NextRunCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrganizationDomain", x => x.Id); + table.ForeignKey( + name: "FK_OrganizationDomain_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationDomain_OrganizationId", + table: "OrganizationDomain", + column: "OrganizationId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OrganizationDomain"); + } +} diff --git a/util/MySqlMigrations/Migrations/20221129004644_OrganizationDomainClaimRenameNextRunCount.Designer.cs b/util/MySqlMigrations/Migrations/20221129004644_OrganizationDomainClaimRenameNextRunCount.Designer.cs new file mode 100644 index 0000000000..0d0694ef5a --- /dev/null +++ b/util/MySqlMigrations/Migrations/20221129004644_OrganizationDomainClaimRenameNextRunCount.Designer.cs @@ -0,0 +1,1725 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20221129004644_OrganizationDomainClaimRenameNextRunCount")] + partial class OrganizationDomainClaimRenameNextRunCount + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestFingerprint") + .HasColumnType("longtext"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("SystemUser") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("JobRunCount") + .HasColumnType("int"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UnknownDeviceVerificationEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20221129004644_OrganizationDomainClaimRenameNextRunCount.cs b/util/MySqlMigrations/Migrations/20221129004644_OrganizationDomainClaimRenameNextRunCount.cs new file mode 100644 index 0000000000..a6b2f4c10b --- /dev/null +++ b/util/MySqlMigrations/Migrations/20221129004644_OrganizationDomainClaimRenameNextRunCount.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +public partial class OrganizationDomainClaimRenameNextRunCount : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "NextRunCount", + table: "OrganizationDomain", + newName: "JobRunCount"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "JobRunCount", + table: "OrganizationDomain", + newName: "NextRunCount"); + } +} diff --git a/util/MySqlMigrations/Migrations/20221209015017_EventsDomainName.Designer.cs b/util/MySqlMigrations/Migrations/20221209015017_EventsDomainName.Designer.cs new file mode 100644 index 0000000000..2a2ef90cc7 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20221209015017_EventsDomainName.Designer.cs @@ -0,0 +1,1731 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20221209015017_EventsDomainName")] + partial class EventsDomainName + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestFingerprint") + .HasColumnType("longtext"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("DomainName") + .HasColumnType("longtext"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("SystemUser") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("JobRunCount") + .HasColumnType("int"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UnknownDeviceVerificationEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20221209015017_EventsDomainName.cs b/util/MySqlMigrations/Migrations/20221209015017_EventsDomainName.cs new file mode 100644 index 0000000000..8b006a63e3 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20221209015017_EventsDomainName.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +public partial class EventsDomainName : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DomainName", + table: "Event", + type: "longtext", + nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DomainName", + table: "Event"); + } +} diff --git a/util/MySqlMigrations/Migrations/20221209192355_OrganizationDomainLastCheckedDate.Designer.cs b/util/MySqlMigrations/Migrations/20221209192355_OrganizationDomainLastCheckedDate.Designer.cs new file mode 100644 index 0000000000..5dc49bb675 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20221209192355_OrganizationDomainLastCheckedDate.Designer.cs @@ -0,0 +1,1734 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20221209192355_OrganizationDomainLastCheckedDate")] + partial class OrganizationDomainLastCheckedDate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestFingerprint") + .HasColumnType("longtext"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("DomainName") + .HasColumnType("longtext"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("SystemUser") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("JobRunCount") + .HasColumnType("int"); + + b.Property("LastCheckedDate") + .HasColumnType("datetime(6)"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UnknownDeviceVerificationEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20221209192355_OrganizationDomainLastCheckedDate.cs b/util/MySqlMigrations/Migrations/20221209192355_OrganizationDomainLastCheckedDate.cs new file mode 100644 index 0000000000..f6884cb5ca --- /dev/null +++ b/util/MySqlMigrations/Migrations/20221209192355_OrganizationDomainLastCheckedDate.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +public partial class OrganizationDomainLastCheckedDate : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LastCheckedDate", + table: "OrganizationDomain", + type: "datetime(6)", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LastCheckedDate", + table: "OrganizationDomain"); + } +} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index 5496ae1ea9..4c9aac6f5d 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -318,6 +318,9 @@ namespace Bit.MySqlMigrations.Migrations b.Property("DeviceType") .HasColumnType("tinyint unsigned"); + b.Property("DomainName") + .HasColumnType("longtext"); + b.Property("GroupId") .HasColumnType("char(36)"); @@ -702,6 +705,43 @@ namespace Bit.MySqlMigrations.Migrations b.ToTable("OrganizationConnection", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DomainName") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("JobRunCount") + .HasColumnType("int"); + + b.Property("LastCheckedDate") + .HasColumnType("datetime(6)"); + + b.Property("NextRunDate") + .HasColumnType("datetime(6)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Txt") + .HasColumnType("longtext"); + + b.Property("VerifiedDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => { b.Property("Id") @@ -1780,6 +1820,17 @@ namespace Bit.MySqlMigrations.Migrations b.Navigation("Organization"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => { b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") @@ -2076,6 +2127,8 @@ namespace Bit.MySqlMigrations.Migrations b.Navigation("Connections"); + b.Navigation("Domains"); + b.Navigation("Groups"); b.Navigation("OrganizationUsers"); diff --git a/util/MySqlMigrations/packages.lock.json b/util/MySqlMigrations/packages.lock.json index 996841866c..209f5237d7 100644 --- a/util/MySqlMigrations/packages.lock.json +++ b/util/MySqlMigrations/packages.lock.json @@ -153,6 +153,14 @@ "System.Xml.XPath.XmlDocument": "4.3.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2740,6 +2748,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/util/PostgresMigrations/HelperScripts/2022-11-03_00_OrganizationDomainClaim.psql b/util/PostgresMigrations/HelperScripts/2022-11-03_00_OrganizationDomainClaim.psql new file mode 100644 index 0000000000..a493c33733 --- /dev/null +++ b/util/PostgresMigrations/HelperScripts/2022-11-03_00_OrganizationDomainClaim.psql @@ -0,0 +1,21 @@ +START TRANSACTION; + +CREATE TABLE "OrganizationDomain" ( + "Id" uuid NOT NULL, + "OrganizationId" uuid NOT NULL, + "Txt" text NULL, + "DomainName" character varying(255) NULL, + "CreationDate" timestamp with time zone NOT NULL, + "VerifiedDate" timestamp with time zone NULL, + "NextRunDate" timestamp with time zone NOT NULL, + "NextRunCount" integer NOT NULL, + CONSTRAINT "PK_OrganizationDomain" PRIMARY KEY ("Id"), + CONSTRAINT "FK_OrganizationDomain_Organization_OrganizationId" FOREIGN KEY ("OrganizationId") REFERENCES "Organization" ("Id") ON DELETE CASCADE +); + +CREATE INDEX "IX_OrganizationDomain_OrganizationId" ON "OrganizationDomain" ("OrganizationId"); + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20221108020928_OrganizationDomainClaim', '6.0.4'); + +COMMIT; \ No newline at end of file diff --git a/util/PostgresMigrations/HelperScripts/2022-11-28_00_OrganizationDomainClaimRenameNextRunCount.psql b/util/PostgresMigrations/HelperScripts/2022-11-28_00_OrganizationDomainClaimRenameNextRunCount.psql new file mode 100644 index 0000000000..357a596a2d --- /dev/null +++ b/util/PostgresMigrations/HelperScripts/2022-11-28_00_OrganizationDomainClaimRenameNextRunCount.psql @@ -0,0 +1,8 @@ +START TRANSACTION; + +ALTER TABLE "OrganizationDomain" RENAME COLUMN "NextRunCount" TO "JobRunCount"; + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20221129032517_OrganizationDomainClaimRenameNextRunCount', '6.0.4'); + +COMMIT; \ No newline at end of file diff --git a/util/PostgresMigrations/HelperScripts/2022-12-08_00_EventsDomainName.psql b/util/PostgresMigrations/HelperScripts/2022-12-08_00_EventsDomainName.psql new file mode 100644 index 0000000000..5143d998d6 --- /dev/null +++ b/util/PostgresMigrations/HelperScripts/2022-12-08_00_EventsDomainName.psql @@ -0,0 +1,9 @@ +START TRANSACTION; + +ALTER TABLE "Event" ADD "DomainName" text NULL; + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20221209020447_EventsDomainName', '6.0.4'); + +COMMIT; + diff --git a/util/PostgresMigrations/HelperScripts/2022-12-09_00_OrganizationDomainLastCheckedDate.psql b/util/PostgresMigrations/HelperScripts/2022-12-09_00_OrganizationDomainLastCheckedDate.psql new file mode 100644 index 0000000000..ff099eee6c --- /dev/null +++ b/util/PostgresMigrations/HelperScripts/2022-12-09_00_OrganizationDomainLastCheckedDate.psql @@ -0,0 +1,9 @@ +START TRANSACTION; + +ALTER TABLE "OrganizationDomain" ADD "LastCheckedDate" timestamp with time zone NULL; + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20221209194623_OrganizationDomainLastCheckedDate', '6.0.4'); + +COMMIT; + diff --git a/util/PostgresMigrations/Migrations/20221025033204_PasswordlessAuthRequestAddApprovedColumn.Designer.cs b/util/PostgresMigrations/Migrations/20221025033204_PasswordlessAuthRequestAddApprovedColumn.Designer.cs index 936ab4648c..478ea452e5 100644 --- a/util/PostgresMigrations/Migrations/20221025033204_PasswordlessAuthRequestAddApprovedColumn.Designer.cs +++ b/util/PostgresMigrations/Migrations/20221025033204_PasswordlessAuthRequestAddApprovedColumn.Designer.cs @@ -38,10 +38,10 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("boolean"); b.Property("AuthenticationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Key") .HasColumnType("text"); @@ -67,7 +67,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("character varying(50)"); b.Property("ResponseDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("ResponseDeviceId") .HasColumnType("uuid"); @@ -96,13 +96,13 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("text"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Data") .HasColumnType("text"); b.Property("DeletedDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Favorites") .HasColumnType("text"); @@ -117,7 +117,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("smallint"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Type") .HasColumnType("smallint"); @@ -140,7 +140,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("ExternalId") .HasMaxLength(300) @@ -153,7 +153,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.HasKey("Id"); @@ -231,7 +231,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Identifier") .HasMaxLength(50) @@ -246,7 +246,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("character varying(255)"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Type") .HasColumnType("smallint"); @@ -267,7 +267,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Email") .HasMaxLength(256) @@ -283,13 +283,13 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("text"); b.Property("LastNotificationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("RecoveryInitiatedDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Status") .HasColumnType("smallint"); @@ -324,7 +324,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("Date") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("DeviceType") .HasColumnType("smallint"); @@ -374,13 +374,13 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Name") .HasColumnType("text"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("UserId") .HasColumnType("uuid"); @@ -403,10 +403,10 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("character varying(200)"); b.Property("ConsumedDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Data") .HasColumnType("text"); @@ -416,7 +416,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("character varying(200)"); b.Property("ExpirationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("SessionId") .HasMaxLength(100) @@ -444,7 +444,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("boolean"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("ExternalId") .HasMaxLength(300) @@ -458,7 +458,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.HasKey("Id"); @@ -493,7 +493,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Email") .HasMaxLength(256) @@ -545,13 +545,13 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("character varying(30)"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Enabled") .HasColumnType("boolean"); b.Property("ExpirationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Gateway") .HasColumnType("smallint"); @@ -587,7 +587,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("character varying(50)"); b.Property("OwnersNotifiedOfAutoscaling") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Plan") .HasMaxLength(50) @@ -606,7 +606,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("text"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Seats") .HasColumnType("integer"); @@ -674,7 +674,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Type") .HasColumnType("smallint"); @@ -720,7 +720,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("character varying(256)"); b.Property("LastSyncDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("OfferedToEmail") .HasMaxLength(256) @@ -742,7 +742,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("boolean"); b.Property("ValidUntil") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.HasKey("Id"); @@ -762,7 +762,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("boolean"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Email") .HasMaxLength(256) @@ -785,7 +785,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("text"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Status") .HasColumnType("smallint"); @@ -811,7 +811,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Data") .HasColumnType("text"); @@ -823,7 +823,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Type") .HasColumnType("smallint"); @@ -862,7 +862,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("text"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Enabled") .HasColumnType("boolean"); @@ -871,7 +871,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("text"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Status") .HasColumnType("smallint"); @@ -890,7 +890,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Key") .HasColumnType("text"); @@ -902,7 +902,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Settings") .HasColumnType("text"); @@ -922,7 +922,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Email") .HasColumnType("text"); @@ -937,7 +937,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Status") .HasColumnType("smallint"); @@ -966,19 +966,19 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("integer"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Data") .HasColumnType("text"); b.Property("DeletionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Disabled") .HasColumnType("boolean"); b.Property("ExpirationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("HideEmail") .HasColumnType("boolean"); @@ -997,7 +997,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("character varying(300)"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Type") .HasColumnType("smallint"); @@ -1023,7 +1023,7 @@ namespace Bit.PostgresMigrations.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Data") .HasColumnType("text"); @@ -1035,7 +1035,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.HasKey("Id"); @@ -1053,7 +1053,7 @@ namespace Bit.PostgresMigrations.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("ExternalId") .HasMaxLength(50) @@ -1113,7 +1113,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("numeric"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Details") .HasMaxLength(100) @@ -1159,7 +1159,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("uuid"); b.Property("AccountRevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("ApiKey") .IsRequired() @@ -1167,7 +1167,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("character varying(30)"); b.Property("CreationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("Culture") .HasMaxLength(10) @@ -1215,7 +1215,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("text"); b.Property("LastFailedLoginDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("LicenseKey") .HasMaxLength(100) @@ -1240,7 +1240,7 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("boolean"); b.Property("PremiumExpirationDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("PrivateKey") .HasColumnType("text"); @@ -1252,10 +1252,10 @@ namespace Bit.PostgresMigrations.Migrations .HasColumnType("text"); b.Property("RenewalReminderDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("RevisionDate") - .HasColumnType("timestamp without time zone"); + .HasColumnType("timestamp with time zone"); b.Property("SecurityStamp") .IsRequired() diff --git a/util/PostgresMigrations/Migrations/20221114192912_OrganizationDomainClaim.Designer.cs b/util/PostgresMigrations/Migrations/20221114192912_OrganizationDomainClaim.Designer.cs new file mode 100644 index 0000000000..bf669a7fae --- /dev/null +++ b/util/PostgresMigrations/Migrations/20221114192912_OrganizationDomainClaim.Designer.cs @@ -0,0 +1,1733 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20221114192912_OrganizationDomainClaim")] + partial class OrganizationDomainClaim + { + 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("ProductVersion", "6.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestFingerprint") + .HasColumnType("text"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("NextRunCount") + .HasColumnType("integer"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UnknownDeviceVerificationEnabled") + .HasColumnType("boolean"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20221114192912_OrganizationDomainClaim.cs b/util/PostgresMigrations/Migrations/20221114192912_OrganizationDomainClaim.cs new file mode 100644 index 0000000000..c94070b19a --- /dev/null +++ b/util/PostgresMigrations/Migrations/20221114192912_OrganizationDomainClaim.cs @@ -0,0 +1,46 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +public partial class OrganizationDomainClaim : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "OrganizationDomain", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OrganizationId = table.Column(type: "uuid", nullable: false), + Txt = table.Column(type: "text", nullable: true), + DomainName = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + CreationDate = table.Column(type: "timestamp with time zone", nullable: false), + VerifiedDate = table.Column(type: "timestamp with time zone", nullable: true), + NextRunDate = table.Column(type: "timestamp with time zone", nullable: false), + NextRunCount = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrganizationDomain", x => x.Id); + table.ForeignKey( + name: "FK_OrganizationDomain_Organization_OrganizationId", + column: x => x.OrganizationId, + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_OrganizationDomain_OrganizationId", + table: "OrganizationDomain", + column: "OrganizationId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "OrganizationDomain"); + } +} diff --git a/util/PostgresMigrations/Migrations/20221129032517_OrganizationDomainClaimRenameNextRunCount.Designer.cs b/util/PostgresMigrations/Migrations/20221129032517_OrganizationDomainClaimRenameNextRunCount.Designer.cs new file mode 100644 index 0000000000..b648dde8f0 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20221129032517_OrganizationDomainClaimRenameNextRunCount.Designer.cs @@ -0,0 +1,1736 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20221129032517_OrganizationDomainClaimRenameNextRunCount")] + partial class OrganizationDomainClaimRenameNextRunCount + { + 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("ProductVersion", "6.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestFingerprint") + .HasColumnType("text"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("SystemUser") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("JobRunCount") + .HasColumnType("integer"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UnknownDeviceVerificationEnabled") + .HasColumnType("boolean"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20221129032517_OrganizationDomainClaimRenameNextRunCount.cs b/util/PostgresMigrations/Migrations/20221129032517_OrganizationDomainClaimRenameNextRunCount.cs new file mode 100644 index 0000000000..65f845ebe2 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20221129032517_OrganizationDomainClaimRenameNextRunCount.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +public partial class OrganizationDomainClaimRenameNextRunCount : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "NextRunCount", + table: "OrganizationDomain", + newName: "JobRunCount"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "JobRunCount", + table: "OrganizationDomain", + newName: "NextRunCount"); + } +} diff --git a/util/PostgresMigrations/Migrations/20221209020447_EventsDomainName.Designer.cs b/util/PostgresMigrations/Migrations/20221209020447_EventsDomainName.Designer.cs new file mode 100644 index 0000000000..5de4580846 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20221209020447_EventsDomainName.Designer.cs @@ -0,0 +1,1742 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20221209020447_EventsDomainName")] + partial class EventsDomainName + { + 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("ProductVersion", "6.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestFingerprint") + .HasColumnType("text"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("DomainName") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("SystemUser") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("JobRunCount") + .HasColumnType("integer"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UnknownDeviceVerificationEnabled") + .HasColumnType("boolean"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20221209020447_EventsDomainName.cs b/util/PostgresMigrations/Migrations/20221209020447_EventsDomainName.cs new file mode 100644 index 0000000000..c5fb22bcf3 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20221209020447_EventsDomainName.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +public partial class EventsDomainName : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DomainName", + table: "Event", + type: "text", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DomainName", + table: "Event"); + } +} diff --git a/util/PostgresMigrations/Migrations/20221209194623_OrganizationDomainLastCheckedDate.Designer.cs b/util/PostgresMigrations/Migrations/20221209194623_OrganizationDomainLastCheckedDate.Designer.cs new file mode 100644 index 0000000000..a0520d308a --- /dev/null +++ b/util/PostgresMigrations/Migrations/20221209194623_OrganizationDomainLastCheckedDate.Designer.cs @@ -0,0 +1,1745 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20221209194623_OrganizationDomainLastCheckedDate")] + partial class OrganizationDomainLastCheckedDate + { + 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("ProductVersion", "6.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestFingerprint") + .HasColumnType("text"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("DomainName") + .HasColumnType("text"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("SystemUser") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("JobRunCount") + .HasColumnType("integer"); + + b.Property("LastCheckedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UnknownDeviceVerificationEnabled") + .HasColumnType("boolean"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Connections"); + + b.Navigation("Domains"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20221209194623_OrganizationDomainLastCheckedDate.cs b/util/PostgresMigrations/Migrations/20221209194623_OrganizationDomainLastCheckedDate.cs new file mode 100644 index 0000000000..0c6b7c2fcf --- /dev/null +++ b/util/PostgresMigrations/Migrations/20221209194623_OrganizationDomainLastCheckedDate.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +public partial class OrganizationDomainLastCheckedDate : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LastCheckedDate", + table: "OrganizationDomain", + type: "timestamp without time zone", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LastCheckedDate", + table: "OrganizationDomain"); + } +} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index d0f836c48d..ae95db8f1b 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -322,6 +322,9 @@ namespace Bit.PostgresMigrations.Migrations b.Property("DeviceType") .HasColumnType("smallint"); + b.Property("DomainName") + .HasColumnType("text"); + b.Property("GroupId") .HasColumnType("uuid"); @@ -707,6 +710,43 @@ namespace Bit.PostgresMigrations.Migrations b.ToTable("OrganizationConnection", (string)null); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DomainName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("JobRunCount") + .HasColumnType("integer"); + + b.Property("LastCheckedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("NextRunDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Txt") + .HasColumnType("text"); + + b.Property("VerifiedDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationDomain", (string)null); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => { b.Property("Id") @@ -1791,6 +1831,17 @@ namespace Bit.PostgresMigrations.Migrations b.Navigation("Organization"); }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationDomain", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Domains") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => { b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") @@ -2087,6 +2138,8 @@ namespace Bit.PostgresMigrations.Migrations b.Navigation("Connections"); + b.Navigation("Domains"); + b.Navigation("Groups"); b.Navigation("OrganizationUsers"); diff --git a/util/PostgresMigrations/packages.lock.json b/util/PostgresMigrations/packages.lock.json index 996841866c..209f5237d7 100644 --- a/util/PostgresMigrations/packages.lock.json +++ b/util/PostgresMigrations/packages.lock.json @@ -153,6 +153,14 @@ "System.Xml.XPath.XmlDocument": "4.3.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2740,6 +2748,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/util/Setup/packages.lock.json b/util/Setup/packages.lock.json index a9543d7815..2dd02b8b61 100644 --- a/util/Setup/packages.lock.json +++ b/util/Setup/packages.lock.json @@ -173,6 +173,14 @@ "dbup-core": "4.5.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2619,6 +2627,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/util/SqlServerEFScaffold/packages.lock.json b/util/SqlServerEFScaffold/packages.lock.json index 63f387a6b1..3135598017 100644 --- a/util/SqlServerEFScaffold/packages.lock.json +++ b/util/SqlServerEFScaffold/packages.lock.json @@ -168,6 +168,14 @@ "resolved": "2.0.123", "contentHash": "RDFF4rBLLmbpi6pwkY7q/M6UXHRJEOerplDGE5jwEkP/JGJnBauAClYavNKJPW1yOTWRPIyfj4is3EaJxQXILQ==" }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2822,6 +2830,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", diff --git a/util/SqliteMigrations/packages.lock.json b/util/SqliteMigrations/packages.lock.json index 996841866c..209f5237d7 100644 --- a/util/SqliteMigrations/packages.lock.json +++ b/util/SqliteMigrations/packages.lock.json @@ -153,6 +153,14 @@ "System.Xml.XPath.XmlDocument": "4.3.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "2hrXR83b5g6/ZMJOA36hXp4t56yb7G1mF3Hg6IkrHxvtyaoXRn2WVdgDPN3V8+GugOlUBbTWXgPaka4dXw1QIg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Fido2": { "type": "Transitive", "resolved": "3.0.1", @@ -2740,6 +2748,7 @@ "Azure.Storage.Queues": "[12.12.0, )", "BitPay.Light": "[1.0.1907, )", "Braintree": "[5.12.0, )", + "DnsClient": "[1.7.0, )", "Fido2.AspNet": "[3.0.1, )", "Handlebars.Net": "[2.1.2, )", "IdentityServer4": "[4.1.2, )", From e6635ff590c9a7150e4669d41cfc1042a7fd8112 Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Wed, 15 Feb 2023 23:52:50 -0500 Subject: [PATCH 22/49] SM-365: Fix Import Secrets Bug (#2703) --- .../Repositories/SecretRepository.cs | 49 ++++++++----------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs index 1bd02992c3..e2226c3038 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs @@ -154,41 +154,32 @@ public class SecretRepository : Repository> ImportAsync(IEnumerable secrets) { - try + using (var scope = ServiceScopeFactory.CreateScope()) { - using (var scope = ServiceScopeFactory.CreateScope()) + var dbContext = GetDatabaseContext(scope); + var entities = new List(); + var projects = secrets + .SelectMany(s => s.Projects ?? Enumerable.Empty()) + .DistinctBy(p => p.Id) + .Select(p => Mapper.Map(p)) + .ToDictionary(p => p.Id, p => p); + + dbContext.AttachRange(projects.Values); + + foreach (var s in secrets) { - var dbContext = GetDatabaseContext(scope); - var entities = new List(); - var projects = secrets - .SelectMany(s => s.Projects ?? Enumerable.Empty()) - .DistinctBy(p => p.Id) - .Select(p => Mapper.Map(p)) - .ToDictionary(p => p.Id, p => p); + var entity = Mapper.Map(s); - dbContext.AttachRange(projects); - - foreach (var s in secrets) + if (s.Projects?.Count > 0) { - var entity = Mapper.Map(s); - - if (s.Projects?.Count > 0) - { - entity.Projects = s.Projects.Select(p => projects[p.Id]).ToList(); - } - - entities.Add(entity); + entity.Projects = s.Projects.Select(p => projects[p.Id]).ToList(); } - await GetDbSet(dbContext).AddRangeAsync(entities); - await dbContext.SaveChangesAsync(); - } - return secrets; - } - catch (Exception e) - { - Console.WriteLine(e); - } + entities.Add(entity); + } + await GetDbSet(dbContext).AddRangeAsync(entities); + await dbContext.SaveChangesAsync(); + } return secrets; } } From 7a209aa3bb43f7bf55490cee06473a813d1a7c3e Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 16 Feb 2023 10:51:02 +0100 Subject: [PATCH 23/49] [SM-501] Add support for revoking access tokens (#2692) * Add support for revoking access tokens --- .../RevokeAccessTokensCommand.cs | 24 ++++ .../SecretsManagerCollectionExtensions.cs | 1 + .../RevokeAccessTokenCommandTests.cs | 39 ++++++ .../Controllers/ServiceAccountsController.cs | 45 +++++- .../Request/RevokeAccessTokensRequest.cs | 7 + .../Interfaces/IRevokeAccessTokensCommand.cs | 8 ++ .../Repositories/IApiKeyRepository.cs | 1 + .../Repositories/ApiKeyRepository.cs | 9 ++ .../Repositories/ApiKeyRepository.cs | 9 ++ .../ApiKey/ApiKey_DeleteByIds.sql | 23 ++++ src/Sql/Sql.sqlproj | 1 + .../ServiceAccountsControllerTests.cs | 130 +++++++++++++++++- .../DbScripts/2023-02-14_00_RevokeApiKeys.sql | 23 ++++ 13 files changed, 315 insertions(+), 5 deletions(-) create mode 100644 bitwarden_license/src/Commercial.Core/SecretsManager/Commands/ServiceAccounts/RevokeAccessTokensCommand.cs create mode 100644 bitwarden_license/test/Commercial.Core.Test/SecretsManager/ServiceAccounts/RevokeAccessTokenCommandTests.cs create mode 100644 src/Api/SecretsManager/Models/Request/RevokeAccessTokensRequest.cs create mode 100644 src/Core/SecretsManager/Commands/ServiceAccounts/Interfaces/IRevokeAccessTokensCommand.cs create mode 100644 src/Sql/SecretsManager/dbo/Stored Procedures/ApiKey/ApiKey_DeleteByIds.sql create mode 100644 util/Migrator/DbScripts/2023-02-14_00_RevokeApiKeys.sql diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/ServiceAccounts/RevokeAccessTokensCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/ServiceAccounts/RevokeAccessTokensCommand.cs new file mode 100644 index 0000000000..7e3474c0c8 --- /dev/null +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/ServiceAccounts/RevokeAccessTokensCommand.cs @@ -0,0 +1,24 @@ +using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces; +using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Repositories; + +namespace Bit.Commercial.Core.SecretsManager.Commands.ServiceAccounts; + +public class RevokeAccessTokensCommand : IRevokeAccessTokensCommand +{ + private readonly IApiKeyRepository _apiKeyRepository; + + public RevokeAccessTokensCommand(IApiKeyRepository apiKeyRepository) + { + _apiKeyRepository = apiKeyRepository; + } + + public async Task RevokeAsync(ServiceAccount serviceAccount, IEnumerable Ids) + { + var accessTokens = await _apiKeyRepository.GetManyByServiceAccountIdAsync(serviceAccount.Id); + + var tokensToDelete = accessTokens.Where(at => Ids.Contains(at.Id)); + + await _apiKeyRepository.DeleteManyAsync(tokensToDelete); + } +} diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs index 81ef53454f..429833089f 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs @@ -26,6 +26,7 @@ public static class SecretsManagerCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/ServiceAccounts/RevokeAccessTokenCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/ServiceAccounts/RevokeAccessTokenCommandTests.cs new file mode 100644 index 0000000000..5f077c4ace --- /dev/null +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/ServiceAccounts/RevokeAccessTokenCommandTests.cs @@ -0,0 +1,39 @@ +using Bit.Commercial.Core.SecretsManager.Commands.ServiceAccounts; +using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Repositories; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Commercial.Core.Test.SecretsManager.ServiceAccounts; + +[SutProviderCustomize] +public class RevokeAccessTokenCommandTests +{ + [Theory] + [BitAutoData] + public async Task RevokeAsyncAsync_Success(ServiceAccount serviceAccount, SutProvider sutProvider) + { + var apiKey1 = new ApiKey + { + Id = Guid.NewGuid(), + ServiceAccountId = serviceAccount.Id + }; + + var apiKey2 = new ApiKey + { + Id = Guid.NewGuid(), + ServiceAccountId = serviceAccount.Id + }; + + sutProvider.GetDependency() + .GetManyByServiceAccountIdAsync(serviceAccount.Id) + .Returns(new List { apiKey1, apiKey2 }); + + await sutProvider.Sut.RevokeAsync(serviceAccount, new List { apiKey1.Id }); + + await sutProvider.GetDependency().Received(1) + .DeleteManyAsync(Arg.Is>(arg => arg.SequenceEqual(new List { apiKey1 }))); + } +} diff --git a/src/Api/SecretsManager/Controllers/ServiceAccountsController.cs b/src/Api/SecretsManager/Controllers/ServiceAccountsController.cs index 175db2a1f2..ccc851c8a2 100644 --- a/src/Api/SecretsManager/Controllers/ServiceAccountsController.cs +++ b/src/Api/SecretsManager/Controllers/ServiceAccountsController.cs @@ -19,20 +19,23 @@ namespace Bit.Api.SecretsManager.Controllers; public class ServiceAccountsController : Controller { private readonly ICurrentContext _currentContext; + private readonly IUserService _userService; + private readonly IServiceAccountRepository _serviceAccountRepository; private readonly IApiKeyRepository _apiKeyRepository; private readonly ICreateAccessTokenCommand _createAccessTokenCommand; private readonly ICreateServiceAccountCommand _createServiceAccountCommand; - private readonly IServiceAccountRepository _serviceAccountRepository; private readonly IUpdateServiceAccountCommand _updateServiceAccountCommand; - private readonly IUserService _userService; + private readonly IRevokeAccessTokensCommand _revokeAccessTokensCommand; public ServiceAccountsController( ICurrentContext currentContext, IUserService userService, IServiceAccountRepository serviceAccountRepository, + IApiKeyRepository apiKeyRepository, ICreateAccessTokenCommand createAccessTokenCommand, - IApiKeyRepository apiKeyRepository, ICreateServiceAccountCommand createServiceAccountCommand, - IUpdateServiceAccountCommand updateServiceAccountCommand) + ICreateServiceAccountCommand createServiceAccountCommand, + IUpdateServiceAccountCommand updateServiceAccountCommand, + IRevokeAccessTokensCommand revokeAccessTokensCommand) { _currentContext = currentContext; _userService = userService; @@ -40,6 +43,7 @@ public class ServiceAccountsController : Controller _apiKeyRepository = apiKeyRepository; _createServiceAccountCommand = createServiceAccountCommand; _updateServiceAccountCommand = updateServiceAccountCommand; + _revokeAccessTokensCommand = revokeAccessTokensCommand; _createAccessTokenCommand = createAccessTokenCommand; } @@ -129,4 +133,37 @@ public class ServiceAccountsController : Controller var result = await _createAccessTokenCommand.CreateAsync(request.ToApiKey(id), userId); return new AccessTokenCreationResponseModel(result); } + + [HttpPost("{id}/access-tokens/revoke")] + public async Task RevokeAccessTokensAsync(Guid id, [FromBody] RevokeAccessTokensRequest request) + { + var userId = _userService.GetProperUserId(User).Value; + var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id); + if (serviceAccount == null) + { + throw new NotFoundException(); + } + + if (!_currentContext.AccessSecretsManager(serviceAccount.OrganizationId)) + { + throw new NotFoundException(); + } + + var orgAdmin = await _currentContext.OrganizationAdmin(serviceAccount.OrganizationId); + var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + + var hasAccess = accessClient switch + { + AccessClientType.NoAccessCheck => true, + AccessClientType.User => await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(id, userId), + _ => false, + }; + + if (!hasAccess) + { + throw new NotFoundException(); + } + + await _revokeAccessTokensCommand.RevokeAsync(serviceAccount, request.Ids); + } } diff --git a/src/Api/SecretsManager/Models/Request/RevokeAccessTokensRequest.cs b/src/Api/SecretsManager/Models/Request/RevokeAccessTokensRequest.cs new file mode 100644 index 0000000000..ecced7a5cd --- /dev/null +++ b/src/Api/SecretsManager/Models/Request/RevokeAccessTokensRequest.cs @@ -0,0 +1,7 @@ +using System.ComponentModel.DataAnnotations; + +public class RevokeAccessTokensRequest +{ + [Required] + public Guid[] Ids { get; set; } +} diff --git a/src/Core/SecretsManager/Commands/ServiceAccounts/Interfaces/IRevokeAccessTokensCommand.cs b/src/Core/SecretsManager/Commands/ServiceAccounts/Interfaces/IRevokeAccessTokensCommand.cs new file mode 100644 index 0000000000..a6aab55d9d --- /dev/null +++ b/src/Core/SecretsManager/Commands/ServiceAccounts/Interfaces/IRevokeAccessTokensCommand.cs @@ -0,0 +1,8 @@ +using Bit.Core.SecretsManager.Entities; + +namespace Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces; + +public interface IRevokeAccessTokensCommand +{ + Task RevokeAsync(ServiceAccount serviceAccount, IEnumerable ids); +} diff --git a/src/Core/SecretsManager/Repositories/IApiKeyRepository.cs b/src/Core/SecretsManager/Repositories/IApiKeyRepository.cs index dc48b5295d..eead6d9d75 100644 --- a/src/Core/SecretsManager/Repositories/IApiKeyRepository.cs +++ b/src/Core/SecretsManager/Repositories/IApiKeyRepository.cs @@ -8,4 +8,5 @@ public interface IApiKeyRepository : IRepository { Task GetDetailsByIdAsync(Guid id); Task> GetManyByServiceAccountIdAsync(Guid id); + Task DeleteManyAsync(IEnumerable objs); } diff --git a/src/Infrastructure.Dapper/SecretsManager/Repositories/ApiKeyRepository.cs b/src/Infrastructure.Dapper/SecretsManager/Repositories/ApiKeyRepository.cs index 8a13eb52c6..c362ae2369 100644 --- a/src/Infrastructure.Dapper/SecretsManager/Repositories/ApiKeyRepository.cs +++ b/src/Infrastructure.Dapper/SecretsManager/Repositories/ApiKeyRepository.cs @@ -42,4 +42,13 @@ public class ApiKeyRepository : Repository, IApiKeyRepository return results.ToList(); } + + public async Task DeleteManyAsync(IEnumerable objs) + { + using var connection = new SqlConnection(ConnectionString); + await connection.QueryAsync( + $"[{Schema}].[ApiKey_DeleteByIds]", + new { Ids = objs.Select(obj => obj.Id).ToGuidIdArrayTVP() }, + commandType: CommandType.StoredProcedure); + } } diff --git a/src/Infrastructure.EntityFramework/SecretsManager/Repositories/ApiKeyRepository.cs b/src/Infrastructure.EntityFramework/SecretsManager/Repositories/ApiKeyRepository.cs index 364415e066..8de2443a1f 100644 --- a/src/Infrastructure.EntityFramework/SecretsManager/Repositories/ApiKeyRepository.cs +++ b/src/Infrastructure.EntityFramework/SecretsManager/Repositories/ApiKeyRepository.cs @@ -36,4 +36,13 @@ public class ApiKeyRepository : Repository>(apiKeys); } + + public async Task DeleteManyAsync(IEnumerable objs) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var entities = objs.Select(obj => Mapper.Map(obj)); + dbContext.RemoveRange(entities); + await dbContext.SaveChangesAsync(); + } } diff --git a/src/Sql/SecretsManager/dbo/Stored Procedures/ApiKey/ApiKey_DeleteByIds.sql b/src/Sql/SecretsManager/dbo/Stored Procedures/ApiKey/ApiKey_DeleteByIds.sql new file mode 100644 index 0000000000..a0c1f2ace2 --- /dev/null +++ b/src/Sql/SecretsManager/dbo/Stored Procedures/ApiKey/ApiKey_DeleteByIds.sql @@ -0,0 +1,23 @@ +CREATE PROCEDURE [dbo].[ApiKey_DeleteByIds] + @Ids [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + DECLARE @BatchSize INT = 100 + + WHILE @BatchSize > 0 + BEGIN + BEGIN TRANSACTION ApiKey_DeleteMany + + DELETE TOP(@BatchSize) AK + FROM + [dbo].[ApiKey] AK + INNER JOIN + @Ids I ON I.Id = AK.Id + + SET @BatchSize = @@ROWCOUNT + + COMMIT TRANSACTION ApiKey_DeleteMany + END +END diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 366026063c..1bed1049a3 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -444,6 +444,7 @@ + diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/ServiceAccountsControllerTests.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/ServiceAccountsControllerTests.cs index 97373422d8..32cae25037 100644 --- a/test/Api.IntegrationTest/SecretsManager/Controllers/ServiceAccountsControllerTests.cs +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/ServiceAccountsControllerTests.cs @@ -1,6 +1,7 @@ using System.Net; using System.Net.Http.Headers; using Bit.Api.IntegrationTest.Factories; +using Bit.Api.IntegrationTest.SecretsManager.Enums; using Bit.Api.Models.Response; using Bit.Api.SecretsManager.Models.Request; using Bit.Api.SecretsManager.Models.Response; @@ -21,9 +22,11 @@ public class ServiceAccountsControllerTest : IClassFixture(); _accessPolicyRepository = _factory.GetService(); + _apiKeyRepository = _factory.GetService(); } public async Task InitializeAsync() @@ -426,6 +430,130 @@ public class ServiceAccountsControllerTest : IClassFixture { + new UserServiceAccountAccessPolicy + { + GrantedServiceAccountId = serviceAccount.Id, + OrganizationUserId = orgUser.Id, + Write = false, + Read = true, + }, + }); + } + + var accessToken = await _apiKeyRepository.CreateAsync(new ApiKey + { + ServiceAccountId = org.Id, + Name = _mockEncryptedString, + ExpireAt = DateTime.UtcNow.AddDays(30), + }); + + var request = new RevokeAccessTokensRequest + { + Ids = new[] { accessToken.Id }, + }; + + var response = await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-tokens/revoke", request); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task RevokeAccessToken_Success(PermissionType permissionType) + { + var (org, _) = await _organizationHelper.Initialize(true, true); + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + if (permissionType == PermissionType.RunAsAdmin) + { + await LoginAsync(_email); + } + else + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + await _accessPolicyRepository.CreateManyAsync(new List { + new UserServiceAccountAccessPolicy + { + GrantedServiceAccountId = serviceAccount.Id, + OrganizationUserId = orgUser.Id, + Write = true, + Read = true, + }, + }); + } + + var accessToken = await _apiKeyRepository.CreateAsync(new ApiKey + { + ServiceAccountId = org.Id, + Name = _mockEncryptedString, + ExpireAt = DateTime.UtcNow.AddDays(30), + }); + + var request = new RevokeAccessTokensRequest + { + Ids = new[] { accessToken.Id }, + }; + + var response = await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/access-tokens/revoke", request); + response.EnsureSuccessStatusCode(); + } + private async Task CreateUserPolicyAsync(Guid userId, Guid serviceAccountId, bool read, bool write) { var policy = new UserServiceAccountAccessPolicy diff --git a/util/Migrator/DbScripts/2023-02-14_00_RevokeApiKeys.sql b/util/Migrator/DbScripts/2023-02-14_00_RevokeApiKeys.sql new file mode 100644 index 0000000000..044b920b53 --- /dev/null +++ b/util/Migrator/DbScripts/2023-02-14_00_RevokeApiKeys.sql @@ -0,0 +1,23 @@ +CREATE OR ALTER PROCEDURE [dbo].[ApiKey_DeleteByIds] + @Ids [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + DECLARE @BatchSize INT = 100 + + WHILE @BatchSize > 0 + BEGIN + BEGIN TRANSACTION ApiKey_DeleteMany + + DELETE TOP(@BatchSize) AK + FROM + [dbo].[ApiKey] AK + INNER JOIN + @Ids I ON I.Id = AK.Id + + SET @BatchSize = @@ROWCOUNT + + COMMIT TRANSACTION ApiKey_DeleteMany + END +END From 6da1b87e00a4904173c338fe40ff6473592de52e Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 16 Feb 2023 15:53:28 +0100 Subject: [PATCH 24/49] Fix name of FixOrganizationDeleteOrgDomain (#2706) --- ...ain.sql => 2023-01-18_00_FixOrganizationDeleteOrgDomain.sql} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename util/Migrator/DbScripts/{2023_01_18_00_FixOrganizationDeleteOrgDomain.sql => 2023-01-18_00_FixOrganizationDeleteOrgDomain.sql} (99%) diff --git a/util/Migrator/DbScripts/2023_01_18_00_FixOrganizationDeleteOrgDomain.sql b/util/Migrator/DbScripts/2023-01-18_00_FixOrganizationDeleteOrgDomain.sql similarity index 99% rename from util/Migrator/DbScripts/2023_01_18_00_FixOrganizationDeleteOrgDomain.sql rename to util/Migrator/DbScripts/2023-01-18_00_FixOrganizationDeleteOrgDomain.sql index eeea4ea7cc..f7bafad474 100644 --- a/util/Migrator/DbScripts/2023_01_18_00_FixOrganizationDeleteOrgDomain.sql +++ b/util/Migrator/DbScripts/2023-01-18_00_FixOrganizationDeleteOrgDomain.sql @@ -111,4 +111,4 @@ WHERE [Id] = @Id COMMIT TRANSACTION Organization_DeleteById -END \ No newline at end of file +END From 770a341a61a3f360b6e30d8dbd77e1bf6f31f207 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 16 Feb 2023 16:24:01 +0100 Subject: [PATCH 25/49] Remove file scoped namespaces instructions (#2708) --- README.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/README.md b/README.md index e6c0dcd85e..a3996383c3 100644 --- a/README.md +++ b/README.md @@ -80,16 +80,3 @@ Consider installing our git pre-commit hook for automatic formatting. ```bash git config --local core.hooksPath .git-hooks ``` - -### File Scoped Namespaces - -We recently migrated to using file scoped namespaces to save some horizontal space. All previous branches will need to update to avoid large merge conflicts using the following steps: - -1. Check out your local Branch -2. Run `git merge 9b7aef0763ad14e229b337c3b5b27cb411009792` -3. Resolve any merge conflicts, commit. -4. Run `dotnet format` -5. Commit -6. Run `git merge -Xours 7f5f010e1eea400300c47f776604ecf46c4b4f2d` -7. Fix Merge conflicts -8. Push From f288787a459e09335e04986ae849697237184576 Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Thu, 16 Feb 2023 09:47:34 -0600 Subject: [PATCH 26/49] [SM-495] Access Policies - Individual Service Account - Project Tab (#2697) * New endpoints to support sa projects tab * Refactor create; Add tests * Add creation request limit --- .../CreateAccessPoliciesCommand.cs | 98 +++--- .../Repositories/AccessPolicyRepository.cs | 50 ++- .../Repositories/ProjectRepository.cs | 18 + .../CreateAccessPoliciesCommandTests.cs | 311 +++++------------- .../Controllers/AccessPoliciesController.cs | 149 +++++++-- .../Request/AccessPoliciesCreateRequest.cs | 20 ++ .../Request/GrantedAccessPolicyRequest.cs | 25 ++ .../Response/AccessPolicyResponseModel.cs | 2 + .../Response/PotentialGranteeResponseModel.cs | 13 + .../ICreateAccessPoliciesCommand.cs | 6 +- .../Repositories/IAccessPolicyRepository.cs | 3 + .../Repositories/IProjectRepository.cs | 1 + .../AccessPoliciesControllerTest.cs | 305 ++++++++++++++++- .../AccessPoliciesControllerTests.cs | 267 ++++++++++++++- 14 files changed, 941 insertions(+), 327 deletions(-) create mode 100644 src/Api/SecretsManager/Models/Request/GrantedAccessPolicyRequest.cs diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs index ffc39099e4..ec9ce3f357 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/AccessPolicies/CreateAccessPoliciesCommand.cs @@ -1,5 +1,4 @@ -using Bit.Core.Context; -using Bit.Core.Enums; +using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Entities; @@ -10,54 +9,33 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand { private readonly IAccessPolicyRepository _accessPolicyRepository; - private readonly ICurrentContext _currentContext; private readonly IProjectRepository _projectRepository; private readonly IServiceAccountRepository _serviceAccountRepository; public CreateAccessPoliciesCommand( IAccessPolicyRepository accessPolicyRepository, - ICurrentContext currentContext, IProjectRepository projectRepository, IServiceAccountRepository serviceAccountRepository) { _accessPolicyRepository = accessPolicyRepository; - _currentContext = currentContext; _projectRepository = projectRepository; _serviceAccountRepository = serviceAccountRepository; } - public async Task> CreateForProjectAsync(Guid projectId, - List accessPolicies, Guid userId) + private static IEnumerable GetDistinctGrantedProjectIds(List accessPolicies) { - var project = await _projectRepository.GetByIdAsync(projectId); - if (project == null || !_currentContext.AccessSecretsManager(project.OrganizationId)) - { - throw new NotFoundException(); - } - - await CheckPermissionAsync(project.OrganizationId, userId, projectId); - CheckForDistinctAccessPolicies(accessPolicies); - await CheckAccessPoliciesDoNotExistAsync(accessPolicies); - - await _accessPolicyRepository.CreateManyAsync(accessPolicies); - return await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(projectId); + var userGrantedIds = accessPolicies.OfType().Select(ap => ap.GrantedProjectId); + var groupGrantedIds = accessPolicies.OfType().Select(ap => ap.GrantedProjectId); + var saGrantedIds = accessPolicies.OfType().Select(ap => ap.GrantedProjectId); + return userGrantedIds.Concat(groupGrantedIds).Concat(saGrantedIds).Distinct(); } - public async Task> CreateForServiceAccountAsync(Guid serviceAccountId, - List accessPolicies, Guid userId) + private static IEnumerable GetDistinctGrantedServiceAccountIds(List accessPolicies) { - var serviceAccount = await _serviceAccountRepository.GetByIdAsync(serviceAccountId); - if (serviceAccount == null || !_currentContext.AccessSecretsManager(serviceAccount.OrganizationId)) - { - throw new NotFoundException(); - } - - await CheckPermissionAsync(serviceAccount.OrganizationId, userId, serviceAccountIdToCheck: serviceAccountId); - CheckForDistinctAccessPolicies(accessPolicies); - await CheckAccessPoliciesDoNotExistAsync(accessPolicies); - - await _accessPolicyRepository.CreateManyAsync(accessPolicies); - return await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(serviceAccountId); + var userGrantedIds = accessPolicies.OfType().Select(ap => ap.GrantedServiceAccountId); + var groupGrantedIds = accessPolicies.OfType() + .Select(ap => ap.GrantedServiceAccountId); + return userGrantedIds.Concat(groupGrantedIds).Distinct(); } private static void CheckForDistinctAccessPolicies(IReadOnlyCollection accessPolicies) @@ -83,6 +61,40 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand } } + public async Task> CreateManyAsync(List accessPolicies, Guid userId, AccessClientType accessType) + { + CheckForDistinctAccessPolicies(accessPolicies); + await CheckAccessPoliciesDoNotExistAsync(accessPolicies); + await CheckCanCreateAsync(accessPolicies, userId, accessType); + return await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + + private async Task CheckCanCreateAsync(List accessPolicies, Guid userId, AccessClientType accessType) + { + var projectIds = GetDistinctGrantedProjectIds(accessPolicies).ToList(); + var serviceAccountIds = GetDistinctGrantedServiceAccountIds(accessPolicies).ToList(); + + if (projectIds.Any()) + { + foreach (var projectId in projectIds) + { + await CheckPermissionAsync(accessType, userId, projectId); + } + } + if (serviceAccountIds.Any()) + { + foreach (var serviceAccountId in serviceAccountIds) + { + await CheckPermissionAsync(accessType, userId, serviceAccountIdToCheck: serviceAccountId); + } + } + + if (!projectIds.Any() && !serviceAccountIds.Any()) + { + throw new BadRequestException("No granted IDs specified"); + } + } + private async Task CheckAccessPoliciesDoNotExistAsync(List accessPolicies) { foreach (var accessPolicy in accessPolicies) @@ -94,14 +106,9 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand } } - private async Task CheckPermissionAsync(Guid organizationId, - Guid userId, - Guid? projectIdToCheck = null, + private async Task CheckPermissionAsync(AccessClientType accessClient, Guid userId, Guid? projectIdToCheck = null, Guid? serviceAccountIdToCheck = null) { - var orgAdmin = await _currentContext.OrganizationAdmin(organizationId); - var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); - bool hasAccess; switch (accessClient) { @@ -109,20 +116,21 @@ public class CreateAccessPoliciesCommand : ICreateAccessPoliciesCommand hasAccess = true; break; case AccessClientType.User: - if (projectIdToCheck != null) + if (projectIdToCheck.HasValue) { hasAccess = await _projectRepository.UserHasWriteAccessToProject(projectIdToCheck.Value, userId); } - else if (serviceAccountIdToCheck != null) + else if (serviceAccountIdToCheck.HasValue) { - hasAccess = await _serviceAccountRepository.UserHasWriteAccessToServiceAccount( - serviceAccountIdToCheck.Value, - userId); + hasAccess = + await _serviceAccountRepository.UserHasWriteAccessToServiceAccount( + serviceAccountIdToCheck.Value, userId); } else { - hasAccess = false; + throw new ArgumentException("No ID to check provided."); } + break; default: hasAccess = false; diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs index 8e5015f8b5..b456309051 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/AccessPolicyRepository.cs @@ -1,10 +1,13 @@ -using AutoMapper; +using System.Linq.Expressions; +using AutoMapper; +using Bit.Core.Enums; using Bit.Core.SecretsManager.Repositories; using Bit.Infrastructure.EntityFramework.Repositories; using Bit.Infrastructure.EntityFramework.SecretsManager.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; + namespace Bit.Commercial.Infrastructure.EntityFramework.SecretsManager.Repositories; public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPolicyRepository @@ -14,6 +17,12 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli { } + private static Expression> UserHasWriteAccessToProject(Guid userId) => + policy => + policy.GrantedProject.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Write) || + policy.GrantedProject.GroupAccessPolicies.Any(ap => + ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Write)); + public async Task> CreateManyAsync(List baseAccessPolicies) { using var scope = ServiceScopeFactory.CreateScope(); @@ -191,16 +200,41 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli } } - private Core.SecretsManager.Entities.BaseAccessPolicy MapToCore(BaseAccessPolicy baseAccessPolicyEntity) + public async Task> GetManyByServiceAccountIdAsync(Guid id, Guid userId, + AccessClientType accessType) { - return baseAccessPolicyEntity switch + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var query = dbContext.ServiceAccountProjectAccessPolicy.Where(ap => + ap.ServiceAccountId == id); + + query = accessType switch + { + AccessClientType.NoAccessCheck => query, + AccessClientType.User => query.Where(UserHasWriteAccessToProject(userId)), + _ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null), + }; + + var entities = await query + .Include(ap => ap.ServiceAccount) + .Include(ap => ap.GrantedProject) + .ToListAsync(); + + return entities.Select(MapToCore); + } + + private Core.SecretsManager.Entities.BaseAccessPolicy MapToCore( + BaseAccessPolicy baseAccessPolicyEntity) => + baseAccessPolicyEntity switch { UserProjectAccessPolicy ap => Mapper.Map(ap), GroupProjectAccessPolicy ap => Mapper.Map(ap), - ServiceAccountProjectAccessPolicy ap => Mapper.Map(ap), - UserServiceAccountAccessPolicy ap => Mapper.Map(ap), - GroupServiceAccountAccessPolicy ap => Mapper.Map(ap), - _ => throw new ArgumentException("Unsupported access policy type") + ServiceAccountProjectAccessPolicy ap => Mapper + .Map(ap), + UserServiceAccountAccessPolicy ap => + Mapper.Map(ap), + GroupServiceAccountAccessPolicy ap => Mapper + .Map(ap), + _ => throw new ArgumentException("Unsupported access policy type"), }; - } } diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs index fac888e18e..20d80d42f6 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs @@ -45,6 +45,24 @@ public class ProjectRepository : Repository>(projects); } + public async Task> GetManyByOrganizationIdWriteAccessAsync( + Guid organizationId, Guid userId, AccessClientType accessType) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var query = dbContext.Project.Where(p => p.OrganizationId == organizationId && p.DeletedDate == null); + + query = accessType switch + { + AccessClientType.NoAccessCheck => query, + AccessClientType.User => query.Where(UserHasWriteAccessToProject(userId)), + _ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null), + }; + + var projects = await query.OrderBy(p => p.RevisionDate).ToListAsync(); + return Mapper.Map>(projects); + } + private static Expression> UserHasReadAccessToProject(Guid userId) => p => p.UserAccessPolicies.Any(ap => ap.OrganizationUser.User.Id == userId && ap.Read) || p.GroupAccessPolicies.Any(ap => ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.User.Id == userId && ap.Read)); diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs index d1c1a29145..2c61e5ec80 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AccessPolicies/CreateAccessPoliciesCommandTests.cs @@ -1,6 +1,6 @@ using Bit.Commercial.Core.SecretsManager.Commands.AccessPolicies; using Bit.Commercial.Core.Test.SecretsManager.Enums; -using Bit.Core.Context; +using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; @@ -17,6 +17,46 @@ namespace Bit.Commercial.Core.Test.SecretsManager.AccessPolicies; [ProjectCustomize] public class CreateAccessPoliciesCommandTests { + private static List MakeGrantedProjectAccessPolicies(Guid grantedProjectId, List userProjectAccessPolicies, + List groupProjectAccessPolicies, + List serviceAccountProjectAccessPolicies) + { + var data = new List(); + foreach (var ap in userProjectAccessPolicies) + { + ap.GrantedProjectId = grantedProjectId; + } + foreach (var ap in groupProjectAccessPolicies) + { + ap.GrantedProjectId = grantedProjectId; + } + foreach (var ap in serviceAccountProjectAccessPolicies) + { + ap.GrantedProjectId = grantedProjectId; + } + data.AddRange(userProjectAccessPolicies); + data.AddRange(groupProjectAccessPolicies); + data.AddRange(serviceAccountProjectAccessPolicies); + return data; + } + + private static List MakeGrantedServiceAccountAccessPolicies(Guid grantedServiceAccountId, List userServiceAccountAccessPolicies, + List groupServiceAccountAccessPolicies) + { + var data = new List(); + foreach (var ap in userServiceAccountAccessPolicies) + { + ap.GrantedServiceAccountId = grantedServiceAccountId; + } + foreach (var ap in groupServiceAccountAccessPolicies) + { + ap.GrantedServiceAccountId = grantedServiceAccountId; + } + data.AddRange(userServiceAccountAccessPolicies); + data.AddRange(groupServiceAccountAccessPolicies); + return data; + } + private static List MakeDuplicate(List data, AccessPolicyType accessPolicyType) { switch (accessPolicyType) @@ -91,97 +131,51 @@ public class CreateAccessPoliciesCommandTests return data; } - private static void SetupAdmin(SutProvider sutProvider, Guid organizationId) - { - sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); - sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(true); - } - - private static void SetupUser(SutProvider sutProvider, Guid organizationId) - { - sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); - sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(false); - } - private static void SetupPermission(SutProvider sutProvider, PermissionType permissionType, Project project, Guid userId) { - switch (permissionType) + if (permissionType == PermissionType.RunAsUserWithPermission) { - case PermissionType.RunAsAdmin: - SetupAdmin(sutProvider, project.OrganizationId); - break; - case PermissionType.RunAsUserWithPermission: - SetupUser(sutProvider, project.OrganizationId); - sutProvider.GetDependency().UserHasWriteAccessToProject(project.Id, userId) - .Returns(true); - break; + sutProvider.GetDependency().UserHasWriteAccessToProject(project.Id, userId) + .Returns(true); } } private static void SetupPermission(SutProvider sutProvider, PermissionType permissionType, ServiceAccount serviceAccount, Guid userId) { - switch (permissionType) + if (permissionType == PermissionType.RunAsUserWithPermission) { - case PermissionType.RunAsAdmin: - SetupAdmin(sutProvider, serviceAccount.OrganizationId); - break; - case PermissionType.RunAsUserWithPermission: - SetupUser(sutProvider, serviceAccount.OrganizationId); - sutProvider.GetDependency() - .UserHasWriteAccessToServiceAccount(serviceAccount.Id, userId).Returns(true); - break; + sutProvider.GetDependency() + .UserHasWriteAccessToServiceAccount(serviceAccount.Id, userId).Returns(true); } } [Theory] [BitAutoData] - public async Task CreateForProject_SmNotEnabled_Throws( + public async Task CreateMany_AlreadyExists_Throws_BadRequestException( Guid userId, Project project, + ServiceAccount serviceAccount, List userProjectAccessPolicies, List groupProjectAccessPolicies, List serviceAccountProjectAccessPolicies, + List userServiceAccountAccessPolicies, + List groupServiceAccountAccessPolicies, SutProvider sutProvider) { - var data = new List(); - data.AddRange(userProjectAccessPolicies); - data.AddRange(groupProjectAccessPolicies); - data.AddRange(serviceAccountProjectAccessPolicies); + var data = MakeGrantedProjectAccessPolicies(project.Id, userProjectAccessPolicies, groupProjectAccessPolicies, + serviceAccountProjectAccessPolicies); + var saData = MakeGrantedServiceAccountAccessPolicies(serviceAccount.Id, userServiceAccountAccessPolicies, groupServiceAccountAccessPolicies); + data = data.Concat(saData).ToList(); - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(false); - - await Assert.ThrowsAsync(() => - sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId)); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateManyAsync(default); - } - - [Theory] - [BitAutoData] - public async Task CreateForProject_AlreadyExists_Throws_BadRequestException( - Guid userId, - Project project, - List userProjectAccessPolicies, - List groupProjectAccessPolicies, - List serviceAccountProjectAccessPolicies, - SutProvider sutProvider) - { - var data = new List(); - data.AddRange(userProjectAccessPolicies); - data.AddRange(groupProjectAccessPolicies); - data.AddRange(serviceAccountProjectAccessPolicies); - - SetupAdmin(sutProvider, project.OrganizationId); - sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); sutProvider.GetDependency().AccessPolicyExists(Arg.Any()) .Returns(true); await Assert.ThrowsAsync(() => - sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId)); + sutProvider.Sut.CreateManyAsync(data, userId, AccessClientType.NoAccessCheck)); - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateManyAsync(default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateManyAsync(default!); } [Theory] @@ -190,29 +184,27 @@ public class CreateAccessPoliciesCommandTests [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)] [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)] [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)] - public async Task CreateForProjectAsync_NotUnique_ThrowsException( + public async Task CreateMany_NotUnique_ThrowsException( AccessPolicyType accessPolicyType, Guid userId, Project project, + ServiceAccount serviceAccount, List userProjectAccessPolicies, List groupProjectAccessPolicies, List serviceAccountProjectAccessPolicies, + List userServiceAccountAccessPolicies, + List groupServiceAccountAccessPolicies, SutProvider sutProvider ) { - var data = new List(); - data.AddRange(userProjectAccessPolicies); - data.AddRange(groupProjectAccessPolicies); - data.AddRange(serviceAccountProjectAccessPolicies); + var data = MakeGrantedProjectAccessPolicies(project.Id, userProjectAccessPolicies, groupProjectAccessPolicies, + serviceAccountProjectAccessPolicies); + var saData = MakeGrantedServiceAccountAccessPolicies(serviceAccount.Id, userServiceAccountAccessPolicies, groupServiceAccountAccessPolicies); + data = data.Concat(saData).ToList(); data = MakeDuplicate(data, accessPolicyType); - SetupAdmin(sutProvider, project.OrganizationId); - sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); - sutProvider.GetDependency().AccessPolicyExists(Arg.Any()) - .Returns(true); - await Assert.ThrowsAsync(() => - sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId)); + sutProvider.Sut.CreateManyAsync(data, userId, AccessClientType.NoAccessCheck)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .CreateManyAsync(Arg.Any>()); @@ -221,24 +213,34 @@ public class CreateAccessPoliciesCommandTests [Theory] [BitAutoData(PermissionType.RunAsAdmin)] [BitAutoData(PermissionType.RunAsUserWithPermission)] - public async Task CreateForProject_Success( + public async Task CreateMany_Success( PermissionType permissionType, Guid userId, Project project, + ServiceAccount serviceAccount, List userProjectAccessPolicies, List groupProjectAccessPolicies, List serviceAccountProjectAccessPolicies, + List userServiceAccountAccessPolicies, + List groupServiceAccountAccessPolicies, SutProvider sutProvider) { - var data = new List(); - data.AddRange(userProjectAccessPolicies); - data.AddRange(groupProjectAccessPolicies); - data.AddRange(serviceAccountProjectAccessPolicies); + var data = MakeGrantedProjectAccessPolicies(project.Id, userProjectAccessPolicies, groupProjectAccessPolicies, + serviceAccountProjectAccessPolicies); + var saData = MakeGrantedServiceAccountAccessPolicies(serviceAccount.Id, userServiceAccountAccessPolicies, groupServiceAccountAccessPolicies); + data = data.Concat(saData).ToList(); - sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); + SetupPermission(sutProvider, permissionType, serviceAccount, userId); SetupPermission(sutProvider, permissionType, project, userId); - await sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId); + if (permissionType == PermissionType.RunAsAdmin) + { + await sutProvider.Sut.CreateManyAsync(data, userId, AccessClientType.NoAccessCheck); + } + else if (permissionType == PermissionType.RunAsUserWithPermission) + { + await sutProvider.Sut.CreateManyAsync(data, userId, AccessClientType.User); + } await sutProvider.GetDependency().Received(1) .CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data))); @@ -246,159 +248,24 @@ public class CreateAccessPoliciesCommandTests [Theory] [BitAutoData] - public async Task CreateForProject_UserNoPermission_ThrowsNotFound( + public async Task CreateMany_UserWithoutPermission_Throws( Guid userId, Project project, - List userProjectAccessPolicies, - List groupProjectAccessPolicies, - List serviceAccountProjectAccessPolicies, - SutProvider sutProvider) - { - var data = new List(); - data.AddRange(userProjectAccessPolicies); - data.AddRange(groupProjectAccessPolicies); - data.AddRange(serviceAccountProjectAccessPolicies); - - SetupUser(sutProvider, project.OrganizationId); - sutProvider.GetDependency().UserHasWriteAccessToProject(project.Id, userId).Returns(false); - sutProvider.GetDependency().GetByIdAsync(project.Id).Returns(project); - - await Assert.ThrowsAsync(() => - sutProvider.Sut.CreateForProjectAsync(project.Id, data, userId)); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateManyAsync(default); - } - - [Theory] - [BitAutoData] - public async Task CreateForServiceAccount_SmNotEnabled_Throws( - Guid userId, ServiceAccount serviceAccount, List userProjectAccessPolicies, List groupProjectAccessPolicies, List serviceAccountProjectAccessPolicies, - SutProvider sutProvider) - { - var data = new List(); - data.AddRange(userProjectAccessPolicies); - data.AddRange(groupProjectAccessPolicies); - data.AddRange(serviceAccountProjectAccessPolicies); - - sutProvider.GetDependency().AccessSecretsManager(Arg.Any()).Returns(false); - - await Assert.ThrowsAsync(() => - sutProvider.Sut.CreateForServiceAccountAsync(serviceAccount.Id, data, userId)); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() - .CreateManyAsync(Arg.Any>()); - } - - [Theory] - [BitAutoData] - public async Task CreateForServiceAccount_AlreadyExists_ThrowsBadRequestException( - Guid userId, - ServiceAccount serviceAccount, - List userProjectAccessPolicies, - List groupProjectAccessPolicies, - List serviceAccountProjectAccessPolicies, - SutProvider sutProvider) - { - var data = new List(); - data.AddRange(userProjectAccessPolicies); - data.AddRange(groupProjectAccessPolicies); - data.AddRange(serviceAccountProjectAccessPolicies); - - SetupAdmin(sutProvider, serviceAccount.OrganizationId); - sutProvider.GetDependency().GetByIdAsync(serviceAccount.Id).Returns(serviceAccount); - sutProvider.GetDependency().AccessPolicyExists(Arg.Any()) - .Returns(true); - - await Assert.ThrowsAsync(() => - sutProvider.Sut.CreateForServiceAccountAsync(serviceAccount.Id, data, userId)); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() - .CreateManyAsync(Arg.Any>()); - } - - [Theory] - [BitAutoData(AccessPolicyType.UserProjectAccessPolicy)] - [BitAutoData(AccessPolicyType.GroupProjectAccessPolicy)] - [BitAutoData(AccessPolicyType.ServiceAccountProjectAccessPolicy)] - [BitAutoData(AccessPolicyType.UserServiceAccountAccessPolicy)] - [BitAutoData(AccessPolicyType.GroupServiceAccountAccessPolicy)] - public async Task CreateForServiceAccount_NotUnique_Throws( - AccessPolicyType accessPolicyType, - Guid userId, - ServiceAccount serviceAccount, - List userProjectAccessPolicies, - List groupProjectAccessPolicies, - List serviceAccountProjectAccessPolicies, - SutProvider sutProvider - ) - { - var data = new List(); - data.AddRange(userProjectAccessPolicies); - data.AddRange(groupProjectAccessPolicies); - data.AddRange(serviceAccountProjectAccessPolicies); - data = MakeDuplicate(data, accessPolicyType); - - SetupAdmin(sutProvider, serviceAccount.OrganizationId); - sutProvider.GetDependency().GetByIdAsync(serviceAccount.Id).Returns(serviceAccount); - sutProvider.GetDependency().AccessPolicyExists(Arg.Any()) - .Returns(true); - - await Assert.ThrowsAsync(() => - sutProvider.Sut.CreateForServiceAccountAsync(serviceAccount.Id, data, userId)); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() - .CreateManyAsync(Arg.Any>()); - } - - - [Theory] - [BitAutoData(PermissionType.RunAsAdmin)] - [BitAutoData(PermissionType.RunAsUserWithPermission)] - public async Task CreateForServiceAccount_Success( - PermissionType permissionType, - Guid userId, - ServiceAccount serviceAccount, List userServiceAccountAccessPolicies, List groupServiceAccountAccessPolicies, SutProvider sutProvider) { - var data = new List(); - data.AddRange(userServiceAccountAccessPolicies); - data.AddRange(groupServiceAccountAccessPolicies); - - sutProvider.GetDependency().GetByIdAsync(serviceAccount.Id).Returns(serviceAccount); - SetupPermission(sutProvider, permissionType, serviceAccount, userId); - - await sutProvider.Sut.CreateForServiceAccountAsync(serviceAccount.Id, data, userId); - - await sutProvider.GetDependency().Received(1) - .CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data))); - } - - [Theory] - [BitAutoData] - public async Task CreateForServiceAccount_UserWithoutPermission_ThrowsNotFound( - Guid userId, - ServiceAccount serviceAccount, - List userServiceAccountAccessPolicies, - List groupServiceAccountAccessPolicies, - SutProvider sutProvider) - { - var data = new List(); - data.AddRange(userServiceAccountAccessPolicies); - data.AddRange(groupServiceAccountAccessPolicies); - - SetupUser(sutProvider, serviceAccount.OrganizationId); - sutProvider.GetDependency().GetByIdAsync(serviceAccount.Id).Returns(serviceAccount); - sutProvider.GetDependency() - .UserHasWriteAccessToServiceAccount(serviceAccount.Id, userId).Returns(false); + var data = MakeGrantedProjectAccessPolicies(project.Id, userProjectAccessPolicies, groupProjectAccessPolicies, + serviceAccountProjectAccessPolicies); + var saData = MakeGrantedServiceAccountAccessPolicies(serviceAccount.Id, userServiceAccountAccessPolicies, groupServiceAccountAccessPolicies); + data = data.Concat(saData).ToList(); await Assert.ThrowsAsync(() => - sutProvider.Sut.CreateForServiceAccountAsync(serviceAccount.Id, data, userId)); + sutProvider.Sut.CreateManyAsync(data, userId, AccessClientType.User)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .CreateManyAsync(Arg.Any>()); diff --git a/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs b/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs index 1707fd33c2..876a71fa3d 100644 --- a/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs +++ b/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs @@ -19,14 +19,15 @@ namespace Bit.Api.SecretsManager.Controllers; [Route("access-policies")] public class AccessPoliciesController : Controller { + private const int _maxBulkCreation = 15; private readonly IAccessPolicyRepository _accessPolicyRepository; - private readonly IServiceAccountRepository _serviceAccountRepository; private readonly ICreateAccessPoliciesCommand _createAccessPoliciesCommand; private readonly ICurrentContext _currentContext; private readonly IDeleteAccessPolicyCommand _deleteAccessPolicyCommand; private readonly IGroupRepository _groupRepository; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IProjectRepository _projectRepository; + private readonly IServiceAccountRepository _serviceAccountRepository; private readonly IUpdateAccessPolicyCommand _updateAccessPolicyCommand; private readonly IUserService _userService; @@ -58,9 +59,21 @@ public class AccessPoliciesController : Controller public async Task CreateProjectAccessPoliciesAsync([FromRoute] Guid id, [FromBody] AccessPoliciesCreateRequest request) { - var userId = _userService.GetProperUserId(User).Value; + if (request.Count() > _maxBulkCreation) + { + throw new BadRequestException($"Can process no more than {_maxBulkCreation} creation requests at once."); + } + + var project = await _projectRepository.GetByIdAsync(id); + if (project == null) + { + throw new NotFoundException(); + } + + var (accessClient, userId) = await GetAccessClientTypeAsync(project.OrganizationId); var policies = request.ToBaseAccessPoliciesForProject(id); - var results = await _createAccessPoliciesCommand.CreateForProjectAsync(id, policies, userId); + await _createAccessPoliciesCommand.CreateManyAsync(policies, userId, accessClient); + var results = await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(id); return new ProjectAccessPoliciesResponseModel(results); } @@ -75,17 +88,31 @@ public class AccessPoliciesController : Controller } [HttpPost("/service-accounts/{id}/access-policies")] - public async Task CreateServiceAccountAccessPoliciesAsync([FromRoute] Guid id, + public async Task CreateServiceAccountAccessPoliciesAsync( + [FromRoute] Guid id, [FromBody] AccessPoliciesCreateRequest request) { - var userId = _userService.GetProperUserId(User).Value; + if (request.Count() > _maxBulkCreation) + { + throw new BadRequestException($"Can process no more than {_maxBulkCreation} creation requests at once."); + } + + var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id); + if (serviceAccount == null) + { + throw new NotFoundException(); + } + + var (accessClient, userId) = await GetAccessClientTypeAsync(serviceAccount.OrganizationId); var policies = request.ToBaseAccessPoliciesForServiceAccount(id); - var results = await _createAccessPoliciesCommand.CreateForServiceAccountAsync(id, policies, userId); + await _createAccessPoliciesCommand.CreateManyAsync(policies, userId, accessClient); + var results = await _accessPolicyRepository.GetManyByGrantedServiceAccountIdAsync(id); return new ServiceAccountAccessPoliciesResponseModel(results); } [HttpGet("/service-accounts/{id}/access-policies")] - public async Task GetServiceAccountAccessPoliciesAsync([FromRoute] Guid id) + public async Task GetServiceAccountAccessPoliciesAsync( + [FromRoute] Guid id) { var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id); await CheckUserHasWriteAccessToServiceAccountAsync(serviceAccount); @@ -94,6 +121,48 @@ public class AccessPoliciesController : Controller return new ServiceAccountAccessPoliciesResponseModel(results); } + [HttpGet("/service-accounts/{id}/granted-policies")] + public async Task> + GetServiceAccountGrantedPoliciesAsync([FromRoute] Guid id) + { + var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id); + if (serviceAccount == null) + { + throw new NotFoundException(); + } + + var (accessClient, userId) = await GetAccessClientTypeAsync(serviceAccount.OrganizationId); + var results = await _accessPolicyRepository.GetManyByServiceAccountIdAsync(id, userId, accessClient); + var responses = results.Select(ap => + new ServiceAccountProjectAccessPolicyResponseModel((ServiceAccountProjectAccessPolicy)ap)); + return new ListResponseModel(responses); + } + + [HttpPost("/service-accounts/{id}/granted-policies")] + public async Task> + CreateServiceAccountGrantedPoliciesAsync([FromRoute] Guid id, + [FromBody] List requests) + { + if (requests.Count > _maxBulkCreation) + { + throw new BadRequestException($"Can process no more than {_maxBulkCreation} creation requests at once."); + } + + var serviceAccount = await _serviceAccountRepository.GetByIdAsync(id); + if (serviceAccount == null) + { + throw new NotFoundException(); + } + + var (accessClient, userId) = await GetAccessClientTypeAsync(serviceAccount.OrganizationId); + var policies = requests.Select(request => request.ToServiceAccountProjectAccessPolicy(id)); + var results = + await _createAccessPoliciesCommand.CreateManyAsync(new List(policies), userId, accessClient); + var responses = results.Select(ap => + new ServiceAccountProjectAccessPolicyResponseModel((ServiceAccountProjectAccessPolicy)ap)); + return new ListResponseModel(responses); + } + [HttpPut("{id}")] public async Task UpdateAccessPolicyAsync([FromRoute] Guid id, [FromBody] AccessPolicyUpdateRequest request) @@ -104,9 +173,11 @@ public class AccessPoliciesController : Controller return result switch { UserProjectAccessPolicy accessPolicy => new UserProjectAccessPolicyResponseModel(accessPolicy), - UserServiceAccountAccessPolicy accessPolicy => new UserServiceAccountAccessPolicyResponseModel(accessPolicy), + UserServiceAccountAccessPolicy accessPolicy => + new UserServiceAccountAccessPolicyResponseModel(accessPolicy), GroupProjectAccessPolicy accessPolicy => new GroupProjectAccessPolicyResponseModel(accessPolicy), - GroupServiceAccountAccessPolicy accessPolicy => new GroupServiceAccountAccessPolicyResponseModel(accessPolicy), + GroupServiceAccountAccessPolicy accessPolicy => new GroupServiceAccountAccessPolicyResponseModel( + accessPolicy), ServiceAccountProjectAccessPolicy accessPolicy => new ServiceAccountProjectAccessPolicyResponseModel( accessPolicy), _ => throw new ArgumentException("Unsupported access policy type provided."), @@ -121,7 +192,8 @@ public class AccessPoliciesController : Controller } [HttpGet("/organizations/{id}/access-policies/people/potential-grantees")] - public async Task> GetPeoplePotentialGranteesAsync([FromRoute] Guid id) + public async Task> GetPeoplePotentialGranteesAsync( + [FromRoute] Guid id) { if (!_currentContext.AccessSecretsManager(id)) { @@ -141,16 +213,10 @@ public class AccessPoliciesController : Controller } [HttpGet("/organizations/{id}/access-policies/service-accounts/potential-grantees")] - public async Task> GetServiceAccountsPotentialGranteesAsync([FromRoute] Guid id) + public async Task> GetServiceAccountsPotentialGranteesAsync( + [FromRoute] Guid id) { - if (!_currentContext.AccessSecretsManager(id)) - { - throw new NotFoundException(); - } - - var userId = _userService.GetProperUserId(User).Value; - var orgAdmin = await _currentContext.OrganizationAdmin(id); - var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + var (accessClient, userId) = await GetAccessClientTypeAsync(id); var serviceAccounts = await _serviceAccountRepository.GetManyByOrganizationIdWriteAccessAsync(id, @@ -162,17 +228,30 @@ public class AccessPoliciesController : Controller return new ListResponseModel(serviceAccountResponses); } + [HttpGet("/organizations/{id}/access-policies/projects/potential-grantees")] + public async Task> GetProjectPotentialGranteesAsync( + [FromRoute] Guid id) + { + var (accessClient, userId) = await GetAccessClientTypeAsync(id); + + var projects = + await _projectRepository.GetManyByOrganizationIdWriteAccessAsync(id, + userId, + accessClient); + var projectResponses = + projects.Select(project => new PotentialGranteeResponseModel(project)); + + return new ListResponseModel(projectResponses); + } + private async Task CheckUserHasWriteAccessToProjectAsync(Project project) { - if (project == null || !_currentContext.AccessSecretsManager(project.OrganizationId)) + if (project == null) { throw new NotFoundException(); } - var userId = _userService.GetProperUserId(User).Value; - var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId); - var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); - + var (accessClient, userId) = await GetAccessClientTypeAsync(project.OrganizationId); var hasAccess = accessClient switch { AccessClientType.NoAccessCheck => true, @@ -188,18 +267,17 @@ public class AccessPoliciesController : Controller private async Task CheckUserHasWriteAccessToServiceAccountAsync(ServiceAccount serviceAccount) { - if (serviceAccount == null || !_currentContext.AccessSecretsManager(serviceAccount.OrganizationId)) + if (serviceAccount == null) { throw new NotFoundException(); } - var userId = _userService.GetProperUserId(User).Value; - var orgAdmin = await _currentContext.OrganizationAdmin(serviceAccount.OrganizationId); - var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + var (accessClient, userId) = await GetAccessClientTypeAsync(serviceAccount.OrganizationId); var hasAccess = accessClient switch { AccessClientType.NoAccessCheck => true, - AccessClientType.User => await _serviceAccountRepository.UserHasWriteAccessToServiceAccount(serviceAccount.Id, userId), + AccessClientType.User => await _serviceAccountRepository.UserHasWriteAccessToServiceAccount( + serviceAccount.Id, userId), _ => false, }; @@ -208,4 +286,17 @@ public class AccessPoliciesController : Controller throw new NotFoundException(); } } + + private async Task<(AccessClientType AccessClientType, Guid UserId)> GetAccessClientTypeAsync(Guid organizationId) + { + if (!_currentContext.AccessSecretsManager(organizationId)) + { + throw new NotFoundException(); + } + + var userId = _userService.GetProperUserId(User).Value; + var orgAdmin = await _currentContext.OrganizationAdmin(organizationId); + var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + return (accessClient, userId); + } } diff --git a/src/Api/SecretsManager/Models/Request/AccessPoliciesCreateRequest.cs b/src/Api/SecretsManager/Models/Request/AccessPoliciesCreateRequest.cs index 689a6c59e1..e4de0a87a6 100644 --- a/src/Api/SecretsManager/Models/Request/AccessPoliciesCreateRequest.cs +++ b/src/Api/SecretsManager/Models/Request/AccessPoliciesCreateRequest.cs @@ -72,6 +72,26 @@ public class AccessPoliciesCreateRequest } return policies; } + + public int Count() + { + var total = 0; + + if (UserAccessPolicyRequests != null) + { + total += UserAccessPolicyRequests.Count(); + } + if (GroupAccessPolicyRequests != null) + { + total += GroupAccessPolicyRequests.Count(); + } + if (ServiceAccountAccessPolicyRequests != null) + { + total += ServiceAccountAccessPolicyRequests.Count(); + } + + return total; + } } public class AccessPolicyRequest diff --git a/src/Api/SecretsManager/Models/Request/GrantedAccessPolicyRequest.cs b/src/Api/SecretsManager/Models/Request/GrantedAccessPolicyRequest.cs new file mode 100644 index 0000000000..0b8f1633c1 --- /dev/null +++ b/src/Api/SecretsManager/Models/Request/GrantedAccessPolicyRequest.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations; +using Bit.Core.SecretsManager.Entities; + +namespace Bit.Api.SecretsManager.Models.Request; + +public class GrantedAccessPolicyRequest +{ + [Required] + public Guid GrantedId { get; set; } + + [Required] + public bool Read { get; set; } + + [Required] + public bool Write { get; set; } + + public ServiceAccountProjectAccessPolicy ToServiceAccountProjectAccessPolicy(Guid serviceAccountId) => + new() + { + ServiceAccountId = serviceAccountId, + GrantedProjectId = GrantedId, + Read = Read, + Write = Write, + }; +} diff --git a/src/Api/SecretsManager/Models/Response/AccessPolicyResponseModel.cs b/src/Api/SecretsManager/Models/Response/AccessPolicyResponseModel.cs index 17337ab699..f273e6d57c 100644 --- a/src/Api/SecretsManager/Models/Response/AccessPolicyResponseModel.cs +++ b/src/Api/SecretsManager/Models/Response/AccessPolicyResponseModel.cs @@ -115,6 +115,7 @@ public class ServiceAccountProjectAccessPolicyResponseModel : BaseAccessPolicyRe ServiceAccountId = accessPolicy.ServiceAccountId; GrantedProjectId = accessPolicy.GrantedProjectId; ServiceAccountName = accessPolicy.ServiceAccount?.Name; + GrantedProjectName = accessPolicy.GrantedProject?.Name; } public ServiceAccountProjectAccessPolicyResponseModel() @@ -125,4 +126,5 @@ public class ServiceAccountProjectAccessPolicyResponseModel : BaseAccessPolicyRe public Guid? ServiceAccountId { get; set; } public string? ServiceAccountName { get; set; } public Guid? GrantedProjectId { get; set; } + public string? GrantedProjectName { get; set; } } diff --git a/src/Api/SecretsManager/Models/Response/PotentialGranteeResponseModel.cs b/src/Api/SecretsManager/Models/Response/PotentialGranteeResponseModel.cs index 7b0632a459..b76af966fe 100644 --- a/src/Api/SecretsManager/Models/Response/PotentialGranteeResponseModel.cs +++ b/src/Api/SecretsManager/Models/Response/PotentialGranteeResponseModel.cs @@ -49,6 +49,19 @@ public class PotentialGranteeResponseModel : ResponseModel Type = "serviceAccount"; } + public PotentialGranteeResponseModel(Project project) + : base(_objectName) + { + if (project == null) + { + throw new ArgumentNullException(nameof(project)); + } + + Id = project.Id.ToString(); + Name = project.Name; + Type = "project"; + } + public PotentialGranteeResponseModel() : base(_objectName) { } diff --git a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs index b1b0bf563f..5baa486e07 100644 --- a/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs +++ b/src/Core/SecretsManager/Commands/AccessPolicies/Interfaces/ICreateAccessPoliciesCommand.cs @@ -1,9 +1,9 @@ -using Bit.Core.SecretsManager.Entities; +using Bit.Core.Enums; +using Bit.Core.SecretsManager.Entities; namespace Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; public interface ICreateAccessPoliciesCommand { - Task> CreateForProjectAsync(Guid projectId, List accessPolicies, Guid userId); - Task> CreateForServiceAccountAsync(Guid serviceAccountId, List accessPolicies, Guid userId); + Task> CreateManyAsync(List accessPolicies, Guid userId, AccessClientType accessType); } diff --git a/src/Core/SecretsManager/Repositories/IAccessPolicyRepository.cs b/src/Core/SecretsManager/Repositories/IAccessPolicyRepository.cs index dbe05074fa..7241c81eb3 100644 --- a/src/Core/SecretsManager/Repositories/IAccessPolicyRepository.cs +++ b/src/Core/SecretsManager/Repositories/IAccessPolicyRepository.cs @@ -1,4 +1,5 @@ #nullable enable +using Bit.Core.Enums; using Bit.Core.SecretsManager.Entities; namespace Bit.Core.SecretsManager.Repositories; @@ -10,6 +11,8 @@ public interface IAccessPolicyRepository Task GetByIdAsync(Guid id); Task> GetManyByGrantedProjectIdAsync(Guid id); Task> GetManyByGrantedServiceAccountIdAsync(Guid id); + Task> GetManyByServiceAccountIdAsync(Guid id, Guid userId, + AccessClientType accessType); Task ReplaceAsync(BaseAccessPolicy baseAccessPolicy); Task DeleteAsync(Guid id); } diff --git a/src/Core/SecretsManager/Repositories/IProjectRepository.cs b/src/Core/SecretsManager/Repositories/IProjectRepository.cs index b742b811c6..3d62d571c3 100644 --- a/src/Core/SecretsManager/Repositories/IProjectRepository.cs +++ b/src/Core/SecretsManager/Repositories/IProjectRepository.cs @@ -6,6 +6,7 @@ namespace Bit.Core.SecretsManager.Repositories; public interface IProjectRepository { Task> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType); + Task> GetManyByOrganizationIdWriteAccessAsync(Guid organizationId, Guid userId, AccessClientType accessType); Task> GetManyByIds(IEnumerable ids); Task GetByIdAsync(Guid id); Task CreateAsync(Project project); diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs index 33cd4d6e52..305d249738 100644 --- a/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/AccessPoliciesControllerTest.cs @@ -157,7 +157,7 @@ public class AccessPoliciesControllerTest : IClassFixture { // Create a new account as a user var (org, _) = await _organizationHelper.Initialize(true, true); - var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); await LoginAsync(email); var project = await _projectRepository.CreateAsync(new Project @@ -207,7 +207,7 @@ public class AccessPoliciesControllerTest : IClassFixture public async Task UpdateAccessPolicy_NoPermission() { // Create a new account as a user - var (org, _) = await _organizationHelper.Initialize(true, true); + await _organizationHelper.Initialize(true, true); var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); await LoginAsync(email); @@ -284,7 +284,7 @@ public class AccessPoliciesControllerTest : IClassFixture public async Task DeleteAccessPolicy_NoPermission() { // Create a new account as a user - var (org, _) = await _organizationHelper.Initialize(true, true); + await _organizationHelper.Initialize(true, true); var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); await LoginAsync(email); @@ -344,8 +344,8 @@ public class AccessPoliciesControllerTest : IClassFixture Assert.NotNull(result); Assert.Empty(result!.UserAccessPolicies); - Assert.Empty(result!.GroupAccessPolicies); - Assert.Empty(result!.ServiceAccountAccessPolicies); + Assert.Empty(result.GroupAccessPolicies); + Assert.Empty(result.ServiceAccountAccessPolicies); } [Theory] @@ -356,6 +356,7 @@ public class AccessPoliciesControllerTest : IClassFixture { var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); await LoginAsync(_email); + var initData = await SetupAccessPolicyRequest(org.Id); var response = await _client.GetAsync($"/projects/{initData.ProjectId}/access-policies"); @@ -366,7 +367,7 @@ public class AccessPoliciesControllerTest : IClassFixture public async Task GetProjectAccessPolicies_NoPermission() { // Create a new account as a user - var (org, _) = await _organizationHelper.Initialize(true, true); + await _organizationHelper.Initialize(true, true); var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); await LoginAsync(email); @@ -434,7 +435,7 @@ public class AccessPoliciesControllerTest : IClassFixture if (permissionType == PermissionType.RunAsUserWithPermission) { - var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); await LoginAsync(email); } @@ -469,10 +470,10 @@ public class AccessPoliciesControllerTest : IClassFixture { // Create a new account as a user var (org, _) = await _organizationHelper.Initialize(true, true); - var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); await LoginAsync(email); - var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + await _serviceAccountRepository.CreateAsync(new ServiceAccount { OrganizationId = org.Id, Name = _mockEncryptedString, @@ -531,7 +532,85 @@ public class AccessPoliciesControllerTest : IClassFixture Assert.NotNull(result?.Data); Assert.NotEmpty(result!.Data); - Assert.Equal(serviceAccount.Id.ToString(), result!.Data.First(x => x.Id == serviceAccount.Id.ToString()).Id); + Assert.Equal(serviceAccount.Id.ToString(), result.Data.First(x => x.Id == serviceAccount.Id.ToString()).Id); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task GetProjectPotentialGrantees_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + + var response = + await _client.GetAsync( + $"/organizations/{org.Id}/access-policies/projects/potential-grantees"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetProjectPotentialGrantees_OnlyReturnsProjectsWithWriteAccess() + { + // Create a new account as a user + var (org, _) = await _organizationHelper.Initialize(true, true); + var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + await _projectRepository.CreateAsync(new Project { OrganizationId = org.Id, Name = _mockEncryptedString }); + + + var response = + await _client.GetAsync( + $"/organizations/{org.Id}/access-policies/projects/potential-grantees"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync>(); + + Assert.NotNull(result?.Data); + Assert.Empty(result!.Data); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task GetProjectPotentialGrantees_Success(PermissionType permissionType) + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + + var project = await _projectRepository.CreateAsync(new Project + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + await _accessPolicyRepository.CreateManyAsync( + new List + { + new UserProjectAccessPolicy + { + GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true, + }, + }); + } + + var response = + await _client.GetAsync( + $"/organizations/{org.Id}/access-policies/projects/potential-grantees"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content.ReadFromJsonAsync>(); + + Assert.NotNull(result?.Data); + Assert.NotEmpty(result!.Data); + Assert.Equal(project.Id.ToString(), result.Data.First(x => x.Id == project.Id.ToString()).Id); } [Theory] @@ -696,14 +775,14 @@ public class AccessPoliciesControllerTest : IClassFixture Assert.NotNull(result); Assert.Empty(result!.UserAccessPolicies); - Assert.Empty(result!.GroupAccessPolicies); + Assert.Empty(result.GroupAccessPolicies); } [Fact] public async Task GetServiceAccountAccessPolicies_NoPermission() { // Create a new account as a user - var (org, _) = await _organizationHelper.Initialize(true, true); + await _organizationHelper.Initialize(true, true); var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); await LoginAsync(email); @@ -763,6 +842,208 @@ public class AccessPoliciesControllerTest : IClassFixture result.UserAccessPolicies.First(x => x.OrganizationUserId == owerOrgUser.Id).OrganizationUserId); } + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task CreateServiceAccountGrantedPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + var request = new List { new() { GrantedId = new Guid() } }; + + var response = + await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/granted-policies", request); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task CreateServiceAccountGrantedPolicies(PermissionType permissionType) + { + var (org, orgUser) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + var ownerOrgUserId = orgUser.Id; + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + var project = await _projectRepository.CreateAsync(new Project + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + var request = + new List { new() { GrantedId = project.Id, Read = true, Write = true } }; + + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, newOrgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + var accessPolicies = new List + { + new UserProjectAccessPolicy + { + GrantedProjectId = project.Id, OrganizationUserId = newOrgUser.Id, Read = true, Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + + + var response = + await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/granted-policies", request); + response.EnsureSuccessStatusCode(); + + var result = await response.Content + .ReadFromJsonAsync>(); + + Assert.NotNull(result); + Assert.NotEmpty(result!.Data); + Assert.Equal(project.Id, result.Data.First().GrantedProjectId); + + var createdAccessPolicy = + await _accessPolicyRepository.GetByIdAsync(result.Data.First().Id); + Assert.NotNull(createdAccessPolicy); + Assert.Equal(result.Data.First().Read, createdAccessPolicy!.Read); + Assert.Equal(result.Data.First().Write, createdAccessPolicy.Write); + Assert.Equal(result.Data.First().Id, createdAccessPolicy.Id); + AssertHelper.AssertRecent(createdAccessPolicy.CreationDate); + AssertHelper.AssertRecent(createdAccessPolicy.RevisionDate); + } + + [Fact] + public async Task CreateServiceAccountGrantedPolicies_NoPermission() + { + // Create a new account as a user + var (org, _) = await _organizationHelper.Initialize(true, true); + var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + var project = await _projectRepository.CreateAsync(new Project + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + var request = + new List { new() { GrantedId = project.Id, Read = true, Write = true } }; + + var response = + await _client.PostAsJsonAsync($"/service-accounts/{serviceAccount.Id}/granted-policies", request); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task GetServiceAccountGrantedPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + var initData = await SetupAccessPolicyRequest(org.Id); + + var response = await _client.GetAsync($"/service-accounts/{initData.ServiceAccountId}/granted-policies"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task GetServiceAccountGrantedPolicies_ReturnsEmpty() + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + var response = await _client.GetAsync($"/service-accounts/{serviceAccount.Id}/granted-policies"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content + .ReadFromJsonAsync>(); + + Assert.NotNull(result); + Assert.Empty(result!.Data); + } + + [Fact] + public async Task GetServiceAccountGrantedPolicies_NoPermission_ReturnsEmpty() + { + // Create a new account as a user + await _organizationHelper.Initialize(true, true); + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var initData = await SetupAccessPolicyRequest(orgUser.OrganizationId); + + var response = await _client.GetAsync($"/service-accounts/{initData.ServiceAccountId}/granted-policies"); + + var result = await response.Content + .ReadFromJsonAsync>(); + + Assert.NotNull(result); + Assert.Empty(result!.Data); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task GetServiceAccountGrantedPolicies(PermissionType permissionType) + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + var initData = await SetupAccessPolicyRequest(org.Id); + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + var accessPolicies = new List + { + new UserProjectAccessPolicy + { + GrantedProjectId = initData.ProjectId, OrganizationUserId = orgUser.Id, Read = true, Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + + var response = await _client.GetAsync($"/service-accounts/{initData.ServiceAccountId}/granted-policies"); + response.EnsureSuccessStatusCode(); + + var result = await response.Content + .ReadFromJsonAsync>(); + + Assert.NotNull(result?.Data); + Assert.NotEmpty(result!.Data); + Assert.Equal(initData.ServiceAccountId, result.Data.First().ServiceAccountId); + Assert.NotNull(result.Data.First().ServiceAccountName); + Assert.NotNull(result.Data.First().GrantedProjectName); + } + private async Task SetupAccessPolicyRequest(Guid organizationId) { var project = await _projectRepository.CreateAsync(new Project diff --git a/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs index 2cd3bf7748..9a3757fb36 100644 --- a/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs +++ b/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs @@ -26,6 +26,30 @@ namespace Bit.Api.Test.SecretsManager.Controllers; [JsonDocumentCustomize] public class AccessPoliciesControllerTests { + private const int _overMax = 16; + + private static AccessPoliciesCreateRequest AddRequestsOverMax(AccessPoliciesCreateRequest request) + { + var newRequests = new List(); + for (var i = 0; i < _overMax; i++) + { + newRequests.Add(new AccessPolicyRequest { GranteeId = new Guid(), Read = true, Write = true }); + } + + request.UserAccessPolicyRequests = newRequests; + return request; + } + + private static List AddRequestsOverMax(List request) + { + for (var i = 0; i < _overMax; i++) + { + request.Add(new GrantedAccessPolicyRequest { GrantedId = new Guid() }); + } + + return request; + } + private static void SetupAdmin(SutProvider sutProvider, Guid organizationId) { sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); @@ -50,7 +74,8 @@ public class AccessPoliciesControllerTests sutProvider.GetDependency().OrganizationUser(default).ReturnsForAnyArgs(true); } - private static void SetupPermission(SutProvider sutProvider, PermissionType permissionType, Guid orgId) + private static void SetupPermission(SutProvider sutProvider, + PermissionType permissionType, Guid orgId) { switch (permissionType) { @@ -61,7 +86,6 @@ public class AccessPoliciesControllerTests SetupUserWithPermission(sutProvider, orgId); break; } - } [Theory] @@ -280,22 +304,139 @@ public class AccessPoliciesControllerTests .GetManyByGrantedServiceAccountIdAsync(Arg.Any()); } + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void GetServiceAccountGrantedPolicies_ReturnsEmptyList( + PermissionType permissionType, + SutProvider sutProvider, + Guid id, ServiceAccount data) + { + sutProvider.GetDependency().GetByIdAsync(data.Id).ReturnsForAnyArgs(data); + + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, data.OrganizationId); + break; + case PermissionType.RunAsUserWithPermission: + SetupUserWithPermission(sutProvider, data.OrganizationId); + sutProvider.GetDependency() + .UserHasWriteAccessToServiceAccount(default, default) + .ReturnsForAnyArgs(true); + break; + } + + var result = await sutProvider.Sut.GetServiceAccountGrantedPoliciesAsync(id); + + await sutProvider.GetDependency().Received(1) + .GetManyByServiceAccountIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), Arg.Any(), + Arg.Any()); + + Assert.Empty(result.Data); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void GetServiceAccountGrantedPolicies_Success( + PermissionType permissionType, + SutProvider sutProvider, + Guid id, + ServiceAccount data, + ServiceAccountProjectAccessPolicy resultAccessPolicy) + { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + switch (permissionType) + { + case PermissionType.RunAsAdmin: + SetupAdmin(sutProvider, data.OrganizationId); + break; + case PermissionType.RunAsUserWithPermission: + SetupUserWithPermission(sutProvider, data.OrganizationId); + break; + } + + sutProvider.GetDependency().GetManyByServiceAccountIdAsync(default, default, default) + .ReturnsForAnyArgs(new List { resultAccessPolicy }); + + var result = await sutProvider.Sut.GetServiceAccountGrantedPoliciesAsync(id); + + await sutProvider.GetDependency().Received(1) + .GetManyByServiceAccountIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), Arg.Any(), + Arg.Any()); + + Assert.NotEmpty(result.Data); + } + + [Theory] + [BitAutoData] + public async void CreateProjectAccessPolicies_RequestMoreThanMax_Throws( + SutProvider sutProvider, + Guid id, + Project mockProject, + UserProjectAccessPolicy data, + AccessPoliciesCreateRequest request) + { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(mockProject); + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + sutProvider.GetDependency().CreateManyAsync(default, default, default) + .ReturnsForAnyArgs(new List { data }); + + request = AddRequestsOverMax(request); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateProjectAccessPoliciesAsync(id, request)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>(), Arg.Any(), Arg.Any()); + } + [Theory] [BitAutoData] public async void CreateProjectAccessPolicies_Success( SutProvider sutProvider, Guid id, + Project mockProject, UserProjectAccessPolicy data, AccessPoliciesCreateRequest request) { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(mockProject); + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); - sutProvider.GetDependency().CreateForProjectAsync(default, default, default) + sutProvider.GetDependency().CreateManyAsync(default, default, default) .ReturnsForAnyArgs(new List { data }); await sutProvider.Sut.CreateProjectAccessPoliciesAsync(id, request); await sutProvider.GetDependency().Received(1) - .CreateForProjectAsync(Arg.Any(), Arg.Any>(), Arg.Any()); + .CreateManyAsync(Arg.Any>(), Arg.Any(), Arg.Any()); + } + + [Theory] + [BitAutoData] + public async void CreateServiceAccountAccessPolicies_RequestMoreThanMax_Throws( + SutProvider sutProvider, + Guid id, + ServiceAccount serviceAccount, + UserServiceAccountAccessPolicy data, + AccessPoliciesCreateRequest request) + { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount); + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + sutProvider.GetDependency() + .CreateManyAsync(default, default, default) + .ReturnsForAnyArgs(new List { data }); + + request = AddRequestsOverMax(request); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateServiceAccountAccessPoliciesAsync(id, request)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>(), Arg.Any(), Arg.Any()); } [Theory] @@ -303,18 +444,68 @@ public class AccessPoliciesControllerTests public async void CreateServiceAccountAccessPolicies_Success( SutProvider sutProvider, Guid id, + ServiceAccount serviceAccount, UserServiceAccountAccessPolicy data, AccessPoliciesCreateRequest request) { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount); + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); sutProvider.GetDependency() - .CreateForServiceAccountAsync(default, default, default) + .CreateManyAsync(default, default, default) .ReturnsForAnyArgs(new List { data }); await sutProvider.Sut.CreateServiceAccountAccessPoliciesAsync(id, request); await sutProvider.GetDependency().Received(1) - .CreateForServiceAccountAsync(Arg.Any(), Arg.Any>(), Arg.Any()); + .CreateManyAsync(Arg.Any>(), Arg.Any(), Arg.Any()); + } + + [Theory] + [BitAutoData] + public async void CreateServiceAccountGrantedPolicies_RequestMoreThanMax_Throws( + SutProvider sutProvider, + Guid id, + ServiceAccount serviceAccount, + ServiceAccountProjectAccessPolicy data, + List request) + { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount); + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + sutProvider.GetDependency() + .CreateManyAsync(default, default, default) + .ReturnsForAnyArgs(new List { data }); + + request = AddRequestsOverMax(request); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateServiceAccountGrantedPoliciesAsync(id, request)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .CreateManyAsync(Arg.Any>(), Arg.Any(), Arg.Any()); + } + + [Theory] + [BitAutoData] + public async void CreateServiceAccountGrantedPolicies_Success( + SutProvider sutProvider, + Guid id, + ServiceAccount serviceAccount, + ServiceAccountProjectAccessPolicy data, + List request) + { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(serviceAccount); + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + sutProvider.GetDependency() + .CreateManyAsync(default, default, default) + .ReturnsForAnyArgs(new List { data }); + + await sutProvider.Sut.CreateServiceAccountGrantedPoliciesAsync(id, request); + + await sutProvider.GetDependency().Received(1) + .CreateManyAsync(Arg.Any>(), Arg.Any(), Arg.Any()); } [Theory] @@ -459,8 +650,9 @@ public class AccessPoliciesControllerTests ServiceAccount mockServiceAccount) { SetupPermission(sutProvider, permissionType, id); - sutProvider.GetDependency().GetManyByOrganizationIdWriteAccessAsync(default, default, default) - .ReturnsForAnyArgs(new List { mockServiceAccount }); + sutProvider.GetDependency() + .GetManyByOrganizationIdWriteAccessAsync(default, default, default) + .ReturnsForAnyArgs(new List { mockServiceAccount }); var result = await sutProvider.Sut.GetServiceAccountsPotentialGranteesAsync(id); @@ -471,4 +663,63 @@ public class AccessPoliciesControllerTests Assert.NotEmpty(result.Data); } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void GetProjectPotentialGrantees_ReturnsEmptyList( + PermissionType permissionType, + SutProvider sutProvider, + Guid id) + { + SetupPermission(sutProvider, permissionType, id); + var result = await sutProvider.Sut.GetProjectPotentialGranteesAsync(id); + + await sutProvider.GetDependency().Received(1) + .GetManyByOrganizationIdWriteAccessAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), + Arg.Is(AssertHelper.AssertPropertyEqual(id)), + Arg.Any()); + + Assert.Empty(result.Data); + } + + [Theory] + [BitAutoData] + public async void GetProjectPotentialGrantees_UserWithoutPermission_Throws( + SutProvider sutProvider, + Guid id) + { + sutProvider.GetDependency().OrganizationAdmin(id).Returns(false); + sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(false); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(Guid.NewGuid()); + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetProjectPotentialGranteesAsync(id)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .GetManyByOrganizationIdWriteAccessAsync(Arg.Any(), Arg.Any(), Arg.Any()); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void GetProjectPotentialGrantees_Success( + PermissionType permissionType, + SutProvider sutProvider, + Guid id, + Project mockProject) + { + SetupPermission(sutProvider, permissionType, id); + sutProvider.GetDependency() + .GetManyByOrganizationIdWriteAccessAsync(default, default, default) + .ReturnsForAnyArgs(new List { mockProject }); + + var result = await sutProvider.Sut.GetProjectPotentialGranteesAsync(id); + + await sutProvider.GetDependency().Received(1) + .GetManyByOrganizationIdWriteAccessAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), + Arg.Is(AssertHelper.AssertPropertyEqual(id)), + Arg.Any()); + + Assert.NotEmpty(result.Data); + } } From bcc2a2a1ce02811f1137916a5ab8c9fbc17994bc Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Thu, 16 Feb 2023 10:05:19 -0600 Subject: [PATCH 27/49] [SM-502] Fix Users with no name (#2698) * Return email if username is empty --- .../Models/Response/AccessPolicyResponseModel.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Api/SecretsManager/Models/Response/AccessPolicyResponseModel.cs b/src/Api/SecretsManager/Models/Response/AccessPolicyResponseModel.cs index f273e6d57c..a298e15b3a 100644 --- a/src/Api/SecretsManager/Models/Response/AccessPolicyResponseModel.cs +++ b/src/Api/SecretsManager/Models/Response/AccessPolicyResponseModel.cs @@ -1,4 +1,5 @@ #nullable enable +using Bit.Core.Entities; using Bit.Core.Models.Api; using Bit.Core.SecretsManager.Entities; @@ -20,6 +21,11 @@ public abstract class BaseAccessPolicyResponseModel : ResponseModel public bool Write { get; set; } public DateTime CreationDate { get; set; } public DateTime RevisionDate { get; set; } + + public string? GetUserDisplayName(User? user) + { + return string.IsNullOrWhiteSpace(user?.Name) ? user?.Email : user?.Name; + } } public class UserProjectAccessPolicyResponseModel : BaseAccessPolicyResponseModel @@ -30,7 +36,7 @@ public class UserProjectAccessPolicyResponseModel : BaseAccessPolicyResponseMode { OrganizationUserId = accessPolicy.OrganizationUserId; GrantedProjectId = accessPolicy.GrantedProjectId; - OrganizationUserName = accessPolicy.User?.Name; + OrganizationUserName = GetUserDisplayName(accessPolicy.User); } public UserProjectAccessPolicyResponseModel() : base(new UserProjectAccessPolicy(), _objectName) @@ -51,7 +57,7 @@ public class UserServiceAccountAccessPolicyResponseModel : BaseAccessPolicyRespo { OrganizationUserId = accessPolicy.OrganizationUserId; GrantedServiceAccountId = accessPolicy.GrantedServiceAccountId; - OrganizationUserName = accessPolicy.User?.Name; + OrganizationUserName = GetUserDisplayName(accessPolicy.User); } public UserServiceAccountAccessPolicyResponseModel() : base(new UserServiceAccountAccessPolicy(), _objectName) From bcaba6652b7dfeb8f70fe83481f62296781d5374 Mon Sep 17 00:00:00 2001 From: Brandon Maharaj Date: Thu, 16 Feb 2023 13:15:45 -0500 Subject: [PATCH 28/49] [SG-1022] Update min password requirements to 12 char (#2677) * fix: update to 10 * work: 12 --- src/SharedWeb/Utilities/ServiceCollectionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index e86ae31211..2d50e48b21 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -341,7 +341,7 @@ public static class ServiceCollectionExtensions { RequireDigit = false, RequireLowercase = false, - RequiredLength = 10, + RequiredLength = 12, RequireNonAlphanumeric = false, RequireUppercase = false }; From ec8476912da6674801097fd74798560c1ae13792 Mon Sep 17 00:00:00 2001 From: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> Date: Thu, 16 Feb 2023 11:42:07 -0800 Subject: [PATCH 29/49] [SM-381] New secrets access (#2629) * [SM-66] Create Secret Database Table (#2144) Objective The purpose of this PR is to create a database table, entity, and repository for the new Secret database table. The new Secret table will use entity framework for all database providers. * [SM-67] Get all secrets by org ID (#2163) Add a controller to fetch secrets associated with an organization ID. To note, the [SecretsManager] attribute makes this controller only available for local development. * [SM-68] Add API endpoints for getting, creating, and editing secrets (#2201) The purpose of this PR is to add API endpoints for getting, creating, and editing secrets for the Secrets Manager project. * Move interfaces to core (#2211) * [SM-63] Read UTC DateTimes from databases via EF and order by revision date (#2206) * Read UTC DateTimes from db and order by revision * Move orderby to repo layer * [SM-185] Add EE_Testing_env to server (#2222) * Sm 104 project Database (#2192) * Project DB addition and sprocs * Adding spaces to the end of each file, fixing minor issues * removing useless comments * Adding soft delete proc to migration * Project EF Scaffold * Additional changes to use EF instead of procedures * Adding dependency injection * Fixing lint errors * Bug fixes * Adding migration scripts, removing sproc files, and setting up Entity framework code * Adding back accidentally deleted sproc * Removing files that shouldn't have been created * Lint * Small changes based on Oscar's rec (#2215) * Migrations for making CreateDate not null * adding space to end of file * Making Revision date not null * dotnet format * Adding nonclustered indexes to SQL * SM-104: Update PR with changes Thomas proposed Co-authored-by: CarleyDiaz-Bitwarden <103955722+CarleyDiaz-Bitwarden@users.noreply.github.com> Co-authored-by: Thomas Avery Co-authored-by: Colton Hurst * Removing org ID from create request body (#2243) * SM-114: Add create & update project endpoints (#2251) * SM-114: Initial commit with create project endpoint (for SM) * SM-114: Add Update Project route (for SM) * SM-114: Fix file encodings * Fix DI issue for SM Project Create/Update commands * Fix import ordering for linter * SM-114: Remove unneeded lines setting DeletedDate, as it should already be null * SM-114: Only have OrgId in route for CreateProject * Remove unneeded using * SM-114: Initial commit with create project endpoint (for SM) * SM-114: Add Update Project route (for SM) * SM-114: Fix file encodings * Fix DI issue for SM Project Create/Update commands * Fix import ordering for linter * SM-114: Remove unneeded lines setting DeletedDate, as it should already be null * SM-114: Only have OrgId in route for CreateProject * Remove unneeded using * Fully remove OrgId from ProjectCreateRequestModel * [SM-64] Soft Delete Secrets (#2253) * Bulk delete secrets with command unit tests * Controller unit tests * Optimize conditionals * SM-64 bulk delete integration test * fix test * SM-64 code review updated * [SM-65] Fix return empty secrets list (#2281) * Secrets return empty list * [SM-246] Use repository in integration test (#2285) * [SM-190] Add integration tests to Secrets (#2292) * Adding integration tests for the SecretsController Co-authored-by: Hinton * Sm 95 - Adding GetProjects endpoint (#2295) * SM-114: Initial commit with create project endpoint (for SM) * SM-114: Add Update Project route (for SM) * SM-114: Fix file encodings * Fix DI issue for SM Project Create/Update commands * Adding GetProjectsByOrg * fixing merge conflicts * fix * Updating to return empty list * removing null check Co-authored-by: Colton Hurst Co-authored-by: CarleyDiaz-Bitwarden <103955722+CarleyDiaz-Bitwarden@users.noreply.github.com> * [SM-191] Create ServiceAccount Table (#2301) * SM-191 Create ServiceAccount Table * [SM-207] API for listing service accounts by organization (#2307) * SM-207 list service accounts by org * SM-96: Add ability to get project by id (#2314) * SM-96: Small change to allow getting project by id * Fix whitespace issue * Add first integration test and fix date bug * Ensure tests are consistent * Add more project controller integration tests * Remove commented delete for now * [SM-187] Create ServiceAccounts (#2323) * SM-187 Create & Update ServiceAccounts * Remove extra new line src/Api/Controllers/ServiceAccountsController.cs Co-authored-by: Oscar Hinton * [SM-218] [SM-219] SM Auth flow (#2297) * SM-282 Delete Projects (#2335) * SM-282 delete & bulk delete projects * Have delete commands return tuple with object * Fix admin project not working after secrets manager changes (#2339) * [SM-150] proj and secrets mapping (#2286) * Beggining of changes for Project Secrets mapping * Beggining of changes for project and secrets mapping * Inital changes to add Mapping table for Project Secrets * Resolve migration not working properly * Indent sql * Changes to try and return projects in the GetManyByOrganizaationIDAsync on SecretRepository. * Changes made with Oscar * Add reversemap * running lint and removing comments * Lint fixes * fixing merge issues * Trying to fix the DB issue * DB fixes * fixes * removing unused space * fixing lint issue * final lint fix I hope * removing manually added sql.sqlproj * Lint changes and fixing the sql proj issues * adding ServiceAccount to sql proj * Removing ON DELETE CASCADE * remove On delete cascade * changes for deleting project and secret inside of the Organization_DeleteById procedure. * changes for deleting project and secret inside of the Organization_DeleteById procedure. * migration changes * Updating constraints * removing void * remove spaces * updating cipherRepo tests to be task instead of void * fixing * fixing * test * fix * fix * changes to remove circular dependency * fixes * sending guid and string name of the project over * Update src/Sql/dbo/Tables/Secret.sql Co-authored-by: Oscar Hinton * Update src/Sql/dbo/Tables/Project.sql Co-authored-by: Oscar Hinton * removing unused code * Potential refactor (#2340) * migrations * Postgres migraiton * Update src/Api/SecretManagerFeatures/Models/Response/SecretResponseModel.cs Co-authored-by: Oscar Hinton * rename file * Update util/Migrator/DbScripts/2022-09-19_00_ProjectSecret.sql Co-authored-by: Oscar Hinton * Lint fixes * removing extra semi colon * removing circular references with projects and secrets * adding back projects * Add ProjectFixture * Update util/Migrator/DbScripts/2022-09-19_00_ProjectSecret.sql Co-authored-by: Oscar Hinton * Update util/Migrator/DbScripts/2022-09-19_00_ProjectSecret.sql Co-authored-by: Oscar Hinton Co-authored-by: CarleyDiaz-Bitwarden <103955722+CarleyDiaz-Bitwarden@users.noreply.github.com> Co-authored-by: Hinton * [SM-300] Access token endpoint (#2377) * [SM-324] Add Organization to JWT claim (#2379) * [SM-259] Add create access token endpoint for service accounts (#2411) * Add create access token for service accounts * [SM-259] Fix create access token scope initialization (#2418) * Fix namespace for ServiceAccount command tests * Remove "this" from SecretsManager requests * Fix have scope be assigned a JSON list * SM-99: Individual Project / Secrets Tab (#2399) Co-authored-by: Oscar Hinton * [SM-361] Add Support for never expiring ApiKeys (#2450) * Update database to support never expiring ApiKey * Update Api to support never expiring ApiKeys * Fix unit test variable naming * Remove required from model * Fix spacing * Add EF migrations * Run dotnet format * Update util/Migrator/DbScripts/2022-11-29_00_ApiKey_Never_Expire.sql Co-authored-by: Oscar Hinton Co-authored-by: Oscar Hinton * [SM-359] Fix project secrets migration (#2443) * [SM-299] Add UseSecretsManager flag (#2413) * [SM-193] Access Policy (#2359) * [SM-371] Fix and re-enable parallel integration tests (#2460) * Fix and re-enable parallel integration tests * Fix package lock files * Move fix to ApiApplicationFactory * Run dotnet restore --force * Run dotnet format * Reset packages.lock.json files * Add project access checks for listing * SM-99: Add CreateSecretWithProject Integration Test (#2452) * Add GetSecretsByProjectAsync endpoint * Add GetManyByProjectIdAsync endpoint * Update response model for GetSecretsByProjectAsync * Include projects when returning secrets by project id * SM-99: Add ability to specify projectId when creating a secret * SM-99: Update tests to accomodate for new create secret parameter * Fix failing test * SM-99: Handle optional projectId for new secret in ToSecret() * SM-99: Filter out deleted secrets on GetManyByProjectIdAsync() and small refactorings * SM-99: make CreateAsync for secret more clear * Add CreateSecretWithProject integration test * Fix CreateSecretWithProject integration test for SM-99 * Run dotnet format * Undo added space * Refactor test * Refactor CreateSecretWithProject API Integration test again * Change to boolean flag * [SM-379] Add SDK device type (#2486) * Add support for service accounts * Improve logic for project repository * Add remaining client types * Experiment with separate enum for access control * Add access checks to update project * Rework AccessClientType * Add access checks to fetching project * Add checks to delete project command (untested) * Remove some service account stuff * Add ServiceAccount to AccessClientType * Change CS8509 to error and 8424 to ignore * Remove unused utcNow * Fix delete tests * SM-73 changes (#2422) * testing * test2 * testing * trying to save the projects associated with the secret * changes * more changes * Fix EF error * Second attempt * Replace AddIfNotExists with Add. * changes * fixing await issue * lint * lint fixes * suggested changes * suggested changes * updating tests * fixing tests 2 * fixing tests * fixing test * fixing test * fixing tests * test * testing * fixing tests for the millionth time * fixing tests * allowing nulls for projectIds, fixing lint * fixing tests Co-authored-by: Hinton * fixing tests * fixing tests * [SM-222] [SM-357] Squash Secrets Manager migrations (#2540) * Fix tables not being cleaned up * Fix migration * Squash secrets manager migrations * Reset EF to pre SM state * Add EF migrations * Fix unified docker * Add missed copy * Fix all unit tests * draft changes to add access checks to secrets * updating code * more changes * fixing issues * updating logic for access checks * updating secrets controller * changes * changes * merging more * changes * updateS * removing unused comment * changes requested by Thomas * more changes suggested by Thomas * making thomas's suggested changes * final changes * Run dotnet format * fixes * run dotnet format * Updating tests * Suggested changes * lint fixes * Test updates * Changes * Fixes for tests, and dotnet format * Fixes * test fixes * changes * fix * fix * test fix * removing duplicate * Removing dupe --------- Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Co-authored-by: Oscar Hinton Co-authored-by: CarleyDiaz-Bitwarden <103955722+CarleyDiaz-Bitwarden@users.noreply.github.com> Co-authored-by: Thomas Avery Co-authored-by: Colton Hurst --- .../Commands/Secrets/CreateSecretCommand.cs | 29 ++- .../Commands/Secrets/DeleteSecretCommand.cs | 54 +++-- .../Commands/Secrets/UpdateSecretCommand.cs | 40 +++- .../Repositories/ProjectRepository.cs | 25 +++ .../Repositories/SecretRepository.cs | 61 ++++-- .../Secrets/CreateSecretCommandTests.cs | 51 ++++- .../Secrets/DeleteSecretCommandTests.cs | 37 +++- .../Secrets/UpdateSecretCommandTests.cs | 61 ++++-- .../Controllers/SecretsController.cs | 78 +++++-- .../SecretsManagerPortingController.cs | 2 +- .../Interfaces/ICreateSecretCommand.cs | 2 +- .../Interfaces/IDeleteSecretCommand.cs | 2 +- .../Interfaces/IUpdateSecretCommand.cs | 2 +- .../Repositories/IProjectRepository.cs | 2 + .../Repositories/ISecretRepository.cs | 7 +- .../Controllers/SecretsControllerTest.cs | 191 ++++++++++++++++-- .../Controllers/SecretsControllerTests.cs | 126 ++++++++++-- 17 files changed, 639 insertions(+), 131 deletions(-) diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Secrets/CreateSecretCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Secrets/CreateSecretCommand.cs index d224247c1d..61558ad228 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Secrets/CreateSecretCommand.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Secrets/CreateSecretCommand.cs @@ -1,4 +1,7 @@ -using Bit.Core.SecretsManager.Commands.Secrets.Interfaces; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.SecretsManager.Commands.Secrets.Interfaces; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; @@ -7,14 +10,34 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.Secrets; public class CreateSecretCommand : ICreateSecretCommand { private readonly ISecretRepository _secretRepository; + private readonly IProjectRepository _projectRepository; + private readonly ICurrentContext _currentContext; - public CreateSecretCommand(ISecretRepository secretRepository) + public CreateSecretCommand(ISecretRepository secretRepository, IProjectRepository projectRepository, ICurrentContext currentContext) { _secretRepository = secretRepository; + _projectRepository = projectRepository; + _currentContext = currentContext; } - public async Task CreateAsync(Secret secret) + public async Task CreateAsync(Secret secret, Guid userId) { + var orgAdmin = await _currentContext.OrganizationAdmin(secret.OrganizationId); + var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + var project = secret.Projects?.FirstOrDefault(); + + var hasAccess = accessClient switch + { + AccessClientType.NoAccessCheck => true, + AccessClientType.User => project != null && await _projectRepository.UserHasWriteAccessToProject(project.Id, userId), + _ => false, + }; + + if (!hasAccess) + { + throw new NotFoundException(); + } + return await _secretRepository.CreateAsync(secret); } } diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Secrets/DeleteSecretCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Secrets/DeleteSecretCommand.cs index c1872717b7..2d59cffe3b 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Secrets/DeleteSecretCommand.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Secrets/DeleteSecretCommand.cs @@ -1,4 +1,5 @@ using Bit.Core.Context; +using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Commands.Secrets.Interfaces; using Bit.Core.SecretsManager.Entities; @@ -10,25 +11,27 @@ public class DeleteSecretCommand : IDeleteSecretCommand { private readonly ICurrentContext _currentContext; private readonly ISecretRepository _secretRepository; + private readonly IProjectRepository _projectRepository; - public DeleteSecretCommand(ICurrentContext currentContext, ISecretRepository secretRepository) + public DeleteSecretCommand(ISecretRepository secretRepository, IProjectRepository projectRepository, ICurrentContext currentContext) { _currentContext = currentContext; _secretRepository = secretRepository; + _projectRepository = projectRepository; } - public async Task>> DeleteSecrets(List ids) + public async Task>> DeleteSecrets(List ids, Guid userId) { - var secrets = await _secretRepository.GetManyByIds(ids); + var secrets = (await _secretRepository.GetManyByIds(ids)).ToList(); - if (secrets?.Any() != true) + if (secrets.Any() != true) { throw new NotFoundException(); } // Ensure all secrets belongs to the same organization var organizationId = secrets.First().OrganizationId; - if (secrets.Any(p => p.OrganizationId != organizationId)) + if (secrets.Any(secret => secret.OrganizationId != organizationId)) { throw new BadRequestException(); } @@ -38,21 +41,46 @@ public class DeleteSecretCommand : IDeleteSecretCommand throw new NotFoundException(); } - var results = ids.Select(id => + var orgAdmin = await _currentContext.OrganizationAdmin(organizationId); + var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + + var results = new List>(); + var deleteIds = new List(); + + foreach (var secret in secrets) { - var secret = secrets.FirstOrDefault(secret => secret.Id == id); - if (secret == null) + var hasAccess = orgAdmin; + + if (secret.Projects != null && secret.Projects?.Count > 0) { - throw new NotFoundException(); + var projectId = secret.Projects.First().Id; + + hasAccess = accessClient switch + { + AccessClientType.NoAccessCheck => true, + AccessClientType.User => await _projectRepository.UserHasWriteAccessToProject(projectId, userId), + _ => false, + }; + } + + if (!hasAccess) + { + results.Add(new Tuple(secret, "access denied")); } - // TODO Once permissions are implemented add check for each secret here. else { - return new Tuple(secret, ""); + deleteIds.Add(secret.Id); + results.Add(new Tuple(secret, "")); } - }).ToList(); + } + + + + if (deleteIds.Count > 0) + { + await _secretRepository.SoftDeleteManyByIdAsync(deleteIds); + } - await _secretRepository.SoftDeleteManyByIdAsync(ids); return results; } } diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Secrets/UpdateSecretCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Secrets/UpdateSecretCommand.cs index 4bd0cf4f31..583208adce 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Secrets/UpdateSecretCommand.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Secrets/UpdateSecretCommand.cs @@ -1,4 +1,6 @@ -using Bit.Core.Exceptions; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Commands.Secrets.Interfaces; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; @@ -8,23 +10,45 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.Secrets; public class UpdateSecretCommand : IUpdateSecretCommand { private readonly ISecretRepository _secretRepository; + private readonly IProjectRepository _projectRepository; + private readonly ICurrentContext _currentContext; - public UpdateSecretCommand(ISecretRepository secretRepository) + public UpdateSecretCommand(ISecretRepository secretRepository, IProjectRepository projectRepository, ICurrentContext currentContext) { _secretRepository = secretRepository; + _projectRepository = projectRepository; + _currentContext = currentContext; } - public async Task UpdateAsync(Secret secret) + public async Task UpdateAsync(Secret updatedSecret, Guid userId) { - var existingSecret = await _secretRepository.GetByIdAsync(secret.Id); - if (existingSecret == null) + var secret = await _secretRepository.GetByIdAsync(updatedSecret.Id); + if (secret == null || !_currentContext.AccessSecretsManager(secret.OrganizationId)) { throw new NotFoundException(); } - secret.OrganizationId = existingSecret.OrganizationId; - secret.CreationDate = existingSecret.CreationDate; - secret.DeletedDate = existingSecret.DeletedDate; + var orgAdmin = await _currentContext.OrganizationAdmin(secret.OrganizationId); + var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + + var project = updatedSecret.Projects?.FirstOrDefault(); + + var hasAccess = accessClient switch + { + AccessClientType.NoAccessCheck => true, + AccessClientType.User => project != null && await _projectRepository.UserHasWriteAccessToProject(project.Id, userId), + _ => false, + }; + + if (!hasAccess) + { + throw new NotFoundException(); + } + + secret.Key = updatedSecret.Key; + secret.Value = updatedSecret.Value; + secret.Note = updatedSecret.Note; + secret.Projects = updatedSecret.Projects; secret.RevisionDate = DateTime.UtcNow; await _secretRepository.UpdateAsync(secret); diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs index 20d80d42f6..782486601c 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs @@ -74,6 +74,9 @@ public class ProjectRepository : Repository> ServiceAccountHasReadAccessToProject(Guid serviceAccountId) => p => p.ServiceAccountAccessPolicies.Any(ap => ap.ServiceAccount.Id == serviceAccountId && ap.Read); + private static Expression> ServiceAccountHasWriteAccessToProject(Guid serviceAccountId) => p => + p.ServiceAccountAccessPolicies.Any(ap => ap.ServiceAccount.Id == serviceAccountId && ap.Write); + public async Task DeleteManyByIdAsync(IEnumerable ids) { using (var scope = ServiceScopeFactory.CreateScope()) @@ -100,6 +103,28 @@ public class ProjectRepository : Repository ServiceAccountHasReadAccessToProject(Guid id, Guid userId) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var query = dbContext.Project + .Where(p => p.Id == id) + .Where(ServiceAccountHasReadAccessToProject(userId)); + + return await query.AnyAsync(); + } + + public async Task ServiceAccountHasWriteAccessToProject(Guid id, Guid userId) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var query = dbContext.Project + .Where(p => p.Id == id) + .Where(ServiceAccountHasWriteAccessToProject(userId)); + + return await query.AnyAsync(); + } + public async Task UserHasReadAccessToProject(Guid id, Guid userId) { using var scope = ServiceScopeFactory.CreateScope(); diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs index e2226c3038..43f1adb591 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs @@ -1,4 +1,6 @@ -using AutoMapper; +using System.Linq.Expressions; +using AutoMapper; +using Bit.Core.Enums; using Bit.Core.SecretsManager.Repositories; using Bit.Infrastructure.EntityFramework; using Bit.Infrastructure.EntityFramework.Repositories; @@ -6,6 +8,7 @@ using Bit.Infrastructure.EntityFramework.SecretsManager.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; + namespace Bit.Commercial.Infrastructure.EntityFramework.SecretsManager.Repositories; public class SecretRepository : Repository, ISecretRepository @@ -34,35 +37,58 @@ public class SecretRepository : Repository ids.Contains(c.Id) && c.DeletedDate == null) + .Include(c => c.Projects) .ToListAsync(); return Mapper.Map>(secrets); } } - public async Task> GetManyByOrganizationIdAsync(Guid organizationId) - { - using (var scope = ServiceScopeFactory.CreateScope()) - { - var dbContext = GetDatabaseContext(scope); - var secrets = await dbContext.Secret - .Where(c => c.OrganizationId == organizationId && c.DeletedDate == null) - .Include("Projects") - .OrderBy(c => c.RevisionDate) - .ToListAsync(); + private static Expression> ServiceAccountHasReadAccessToSecret(Guid serviceAccountId) => s => + s.Projects.Any(p => + p.ServiceAccountAccessPolicies.Any(ap => ap.ServiceAccount.Id == serviceAccountId && ap.Read)); - return Mapper.Map>(secrets); - } + private static Expression> UserHasReadAccessToSecret(Guid userId) => s => + s.Projects.Any(p => + p.UserAccessPolicies.Any(ap => ap.OrganizationUser.UserId == userId && ap.Read) || + p.GroupAccessPolicies.Any(ap => + ap.Group.GroupUsers.Any(gu => gu.OrganizationUser.UserId == userId && ap.Read))); + + + public async Task> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var query = dbContext.Secret.Include(c => c.Projects).Where(c => c.OrganizationId == organizationId && c.DeletedDate == null); + + query = accessType switch + { + AccessClientType.NoAccessCheck => query, + AccessClientType.User => query.Where(UserHasReadAccessToSecret(userId)), + AccessClientType.ServiceAccount => query.Where(ServiceAccountHasReadAccessToSecret(userId)), + _ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null), + }; + + var secrets = await query.OrderBy(c => c.RevisionDate).ToListAsync(); + return Mapper.Map>(secrets); } - public async Task> GetManyByProjectIdAsync(Guid projectId) + public async Task> GetManyByProjectIdAsync(Guid projectId, Guid userId, AccessClientType accessType) { using (var scope = ServiceScopeFactory.CreateScope()) { var dbContext = GetDatabaseContext(scope); - var secrets = await dbContext.Secret - .Where(s => s.Projects.Any(p => p.Id == projectId) && s.DeletedDate == null).Include("Projects") - .OrderBy(s => s.RevisionDate).ToListAsync(); + var query = dbContext.Secret.Include(s => s.Projects) + .Where(s => s.Projects.Any(p => p.Id == projectId) && s.DeletedDate == null); + query = accessType switch + { + AccessClientType.NoAccessCheck => query, + AccessClientType.User => query.Where(UserHasReadAccessToSecret(userId)), + AccessClientType.ServiceAccount => query.Where(ServiceAccountHasReadAccessToSecret(userId)), + _ => throw new ArgumentOutOfRangeException(nameof(accessType), accessType, null), + }; + + var secrets = await query.OrderBy(s => s.RevisionDate).ToListAsync(); return Mapper.Map>(secrets); } } @@ -96,6 +122,7 @@ public class SecretRepository : Repository(secret); + var entity = await dbContext.Secret .Include("Projects") .FirstAsync(s => s.Id == secret.Id); diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/CreateSecretCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/CreateSecretCommandTests.cs index 9accbc53fa..78b8c2a02e 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/CreateSecretCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/CreateSecretCommandTests.cs @@ -1,4 +1,7 @@ using Bit.Commercial.Core.SecretsManager.Commands.Secrets; +using Bit.Commercial.Core.Test.SecretsManager.Enums; +using Bit.Core.Context; +using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; using Bit.Core.Test.SecretsManager.AutoFixture.SecretsFixture; @@ -14,14 +17,54 @@ namespace Bit.Commercial.Core.Test.SecretsManager.Secrets; public class CreateSecretCommandTests { [Theory] - [BitAutoData] - public async Task CreateAsync_CallsCreate(Secret data, - SutProvider sutProvider) + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async Task CreateAsync_Success(PermissionType permissionType, Secret data, + SutProvider sutProvider, Guid userId, Project mockProject) { - await sutProvider.Sut.CreateAsync(data); + data.Projects = new List() { mockProject }; + + if (permissionType == PermissionType.RunAsAdmin) + { + sutProvider.GetDependency().OrganizationAdmin(data.OrganizationId).Returns(true); + } + else + { + sutProvider.GetDependency().OrganizationAdmin(data.OrganizationId).Returns(false); + sutProvider.GetDependency().UserHasWriteAccessToProject((Guid)(data.Projects?.First().Id), userId).Returns(true); + } + + await sutProvider.Sut.CreateAsync(data, userId); await sutProvider.GetDependency().Received(1) .CreateAsync(data); } + + + [Theory] + [BitAutoData] + public async Task CreateAsync_UserWithoutPermission_ThrowsNotFound(Secret data, + SutProvider sutProvider, Guid userId, Project mockProject) + { + data.Projects = new List() { mockProject }; + + sutProvider.GetDependency().OrganizationAdmin(data.OrganizationId).Returns(false); + sutProvider.GetDependency().UserHasWriteAccessToProject((Guid)(data.Projects?.First().Id), userId).Returns(false); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateAsync(data, userId)); + } + + [Theory] + [BitAutoData] + public async Task CreateAsync_NoProjects_User_ThrowsNotFound(Secret data, + SutProvider sutProvider, Guid userId) + { + data.Projects = null; + sutProvider.GetDependency().OrganizationAdmin(data.OrganizationId).Returns(false); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.CreateAsync(data, userId)); + } } diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/DeleteSecretCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/DeleteSecretCommandTests.cs index cee70548e7..8026fcd8ee 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/DeleteSecretCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/DeleteSecretCommandTests.cs @@ -1,16 +1,20 @@ using Bit.Commercial.Core.SecretsManager.Commands.Secrets; +using Bit.Commercial.Core.Test.SecretsManager.Enums; using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; +using Bit.Test.Common.Helpers; using NSubstitute; using Xunit; namespace Bit.Commercial.Core.Test.SecretsManager.Secrets; [SutProviderCustomize] +[ProjectCustomize] public class DeleteSecretCommandTests { [Theory] @@ -20,7 +24,7 @@ public class DeleteSecretCommandTests { sutProvider.GetDependency().GetManyByIds(data).Returns(new List()); - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteSecrets(data)); + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteSecrets(data, default)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().SoftDeleteManyByIdAsync(default); } @@ -36,22 +40,39 @@ public class DeleteSecretCommandTests }; sutProvider.GetDependency().GetManyByIds(data).Returns(new List() { secret }); - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteSecrets(data)); + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteSecrets(data, default)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().SoftDeleteManyByIdAsync(default); } [Theory] - [BitAutoData] - public async Task DeleteSecrets_Success(List data, - SutProvider sutProvider) + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async Task DeleteSecrets_Success(PermissionType permissionType, List data, + SutProvider sutProvider, Guid userId, Guid organizationId, Project mockProject) { + List projects = null; + + if (permissionType == PermissionType.RunAsAdmin) + { + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(true); + } + else + { + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(false); + sutProvider.GetDependency().UserHasWriteAccessToProject(mockProject.Id, userId).Returns(true); + projects = new List() { mockProject }; + } + + var secrets = new List(); foreach (Guid id in data) { var secret = new Secret() { - Id = id + Id = id, + OrganizationId = organizationId, + Projects = projects }; secrets.Add(secret); } @@ -59,9 +80,9 @@ public class DeleteSecretCommandTests sutProvider.GetDependency().GetManyByIds(data).Returns(secrets); sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - var results = await sutProvider.Sut.DeleteSecrets(data); + var results = await sutProvider.Sut.DeleteSecrets(data, userId); + await sutProvider.GetDependency().Received(1).SoftDeleteManyByIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data))); - await sutProvider.GetDependency().Received(1).SoftDeleteManyByIdAsync(Arg.Is(data)); foreach (var result in results) { Assert.Equal("", result.Item2); diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/UpdateSecretCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/UpdateSecretCommandTests.cs index 683868d7ed..faa6e7ec5e 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/UpdateSecretCommandTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Secrets/UpdateSecretCommandTests.cs @@ -1,7 +1,10 @@ using Bit.Commercial.Core.SecretsManager.Commands.Secrets; +using Bit.Commercial.Core.Test.SecretsManager.Enums; +using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; using Bit.Core.Test.SecretsManager.AutoFixture.SecretsFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -13,23 +16,38 @@ namespace Bit.Commercial.Core.Test.SecretsManager.Secrets; [SutProviderCustomize] [SecretCustomize] +[ProjectCustomize] public class UpdateSecretCommandTests { [Theory] [BitAutoData] public async Task UpdateAsync_SecretDoesNotExist_ThrowsNotFound(Secret data, SutProvider sutProvider) { - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(data)); + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateAsync(data, default)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().UpdateAsync(default); } [Theory] - [BitAutoData] - public async Task UpdateAsync_CallsReplaceAsync(Secret data, SutProvider sutProvider) + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async Task UpdateAsync_Success(PermissionType permissionType, Secret data, SutProvider sutProvider, Guid userId, Project mockProject) { + sutProvider.GetDependency().AccessSecretsManager(data.OrganizationId).Returns(true); + + if (permissionType == PermissionType.RunAsAdmin) + { + sutProvider.GetDependency().OrganizationAdmin(data.OrganizationId).Returns(true); + } + else + { + data.Projects = new List() { mockProject }; + sutProvider.GetDependency().OrganizationAdmin(data.OrganizationId).Returns(false); + sutProvider.GetDependency().UserHasWriteAccessToProject((Guid)(data.Projects?.First().Id), userId).Returns(true); + } + sutProvider.GetDependency().GetByIdAsync(data.Id).Returns(data); - await sutProvider.Sut.UpdateAsync(data); + await sutProvider.Sut.UpdateAsync(data, userId); await sutProvider.GetDependency().Received(1) .UpdateAsync(data); @@ -37,11 +55,14 @@ public class UpdateSecretCommandTests [Theory] [BitAutoData] - public async Task UpdateAsync_DoesNotModifyOrganizationId(Secret existingSecret, SutProvider sutProvider) + public async Task UpdateAsync_DoesNotModifyOrganizationId(Secret existingSecret, SutProvider sutProvider, Guid userId) { - sutProvider.GetDependency().GetByIdAsync(existingSecret.Id).Returns(existingSecret); - var updatedOrgId = Guid.NewGuid(); + sutProvider.GetDependency().OrganizationAdmin(existingSecret.OrganizationId).Returns(true); + sutProvider.GetDependency().AccessSecretsManager(existingSecret.OrganizationId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(existingSecret.Id).Returns(existingSecret); + sutProvider.GetDependency().OrganizationAdmin(updatedOrgId).Returns(true); + var secretUpdate = new Secret() { OrganizationId = updatedOrgId, @@ -49,7 +70,7 @@ public class UpdateSecretCommandTests Key = existingSecret.Key, }; - var result = await sutProvider.Sut.UpdateAsync(secretUpdate); + var result = await sutProvider.Sut.UpdateAsync(secretUpdate, userId); Assert.Equal(existingSecret.OrganizationId, result.OrganizationId); Assert.NotEqual(existingSecret.OrganizationId, updatedOrgId); @@ -57,9 +78,11 @@ public class UpdateSecretCommandTests [Theory] [BitAutoData] - public async Task UpdateAsync_DoesNotModifyCreationDate(Secret existingSecret, SutProvider sutProvider) + public async Task UpdateAsync_DoesNotModifyCreationDate(Secret existingSecret, SutProvider sutProvider, Guid userId) { + sutProvider.GetDependency().AccessSecretsManager(existingSecret.OrganizationId).Returns(true); sutProvider.GetDependency().GetByIdAsync(existingSecret.Id).Returns(existingSecret); + sutProvider.GetDependency().OrganizationAdmin(existingSecret.OrganizationId).Returns(true); var updatedCreationDate = DateTime.UtcNow; var secretUpdate = new Secret() @@ -67,9 +90,10 @@ public class UpdateSecretCommandTests CreationDate = updatedCreationDate, Id = existingSecret.Id, Key = existingSecret.Key, + OrganizationId = existingSecret.OrganizationId }; - var result = await sutProvider.Sut.UpdateAsync(secretUpdate); + var result = await sutProvider.Sut.UpdateAsync(secretUpdate, userId); Assert.Equal(existingSecret.CreationDate, result.CreationDate); Assert.NotEqual(existingSecret.CreationDate, updatedCreationDate); @@ -77,9 +101,11 @@ public class UpdateSecretCommandTests [Theory] [BitAutoData] - public async Task UpdateAsync_DoesNotModifyDeletionDate(Secret existingSecret, SutProvider sutProvider) + public async Task UpdateAsync_DoesNotModifyDeletionDate(Secret existingSecret, SutProvider sutProvider, Guid userId) { + sutProvider.GetDependency().AccessSecretsManager(existingSecret.OrganizationId).Returns(true); sutProvider.GetDependency().GetByIdAsync(existingSecret.Id).Returns(existingSecret); + sutProvider.GetDependency().OrganizationAdmin(existingSecret.OrganizationId).Returns(true); var updatedDeletionDate = DateTime.UtcNow; var secretUpdate = new Secret() @@ -87,9 +113,10 @@ public class UpdateSecretCommandTests DeletedDate = updatedDeletionDate, Id = existingSecret.Id, Key = existingSecret.Key, + OrganizationId = existingSecret.OrganizationId }; - var result = await sutProvider.Sut.UpdateAsync(secretUpdate); + var result = await sutProvider.Sut.UpdateAsync(secretUpdate, userId); Assert.Equal(existingSecret.DeletedDate, result.DeletedDate); Assert.NotEqual(existingSecret.DeletedDate, updatedDeletionDate); @@ -98,9 +125,12 @@ public class UpdateSecretCommandTests [Theory] [BitAutoData] - public async Task UpdateAsync_RevisionDateIsUpdatedToUtcNow(Secret existingSecret, SutProvider sutProvider) + public async Task UpdateAsync_RevisionDateIsUpdatedToUtcNow(Secret existingSecret, SutProvider sutProvider, Guid userId) { + sutProvider.GetDependency().OrganizationAdmin(existingSecret.OrganizationId).Returns(true); + sutProvider.GetDependency().AccessSecretsManager(existingSecret.OrganizationId).Returns(true); sutProvider.GetDependency().GetByIdAsync(existingSecret.Id).Returns(existingSecret); + sutProvider.GetDependency().OrganizationAdmin(existingSecret.OrganizationId).Returns(true); var updatedRevisionDate = DateTime.UtcNow.AddDays(10); var secretUpdate = new Secret() @@ -108,11 +138,12 @@ public class UpdateSecretCommandTests RevisionDate = updatedRevisionDate, Id = existingSecret.Id, Key = existingSecret.Key, + OrganizationId = existingSecret.OrganizationId }; - var result = await sutProvider.Sut.UpdateAsync(secretUpdate); + var result = await sutProvider.Sut.UpdateAsync(secretUpdate, userId); - Assert.NotEqual(existingSecret.RevisionDate, result.RevisionDate); + Assert.NotEqual(secretUpdate.RevisionDate, result.RevisionDate); AssertHelper.AssertRecent(result.RevisionDate); } } diff --git a/src/Api/SecretsManager/Controllers/SecretsController.cs b/src/Api/SecretsManager/Controllers/SecretsController.cs index 3ddf4699d7..7418fe0a17 100644 --- a/src/Api/SecretsManager/Controllers/SecretsController.cs +++ b/src/Api/SecretsManager/Controllers/SecretsController.cs @@ -2,9 +2,12 @@ using Bit.Api.SecretsManager.Models.Request; using Bit.Api.SecretsManager.Models.Response; using Bit.Core.Context; +using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Commands.Secrets.Interfaces; +using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -16,22 +19,21 @@ public class SecretsController : Controller { private readonly ICurrentContext _currentContext; private readonly ISecretRepository _secretRepository; + private readonly IProjectRepository _projectRepository; private readonly ICreateSecretCommand _createSecretCommand; private readonly IUpdateSecretCommand _updateSecretCommand; private readonly IDeleteSecretCommand _deleteSecretCommand; + private readonly IUserService _userService; - public SecretsController( - ICurrentContext currentContext, - ISecretRepository secretRepository, - ICreateSecretCommand createSecretCommand, - IUpdateSecretCommand updateSecretCommand, - IDeleteSecretCommand deleteSecretCommand) + public SecretsController(ISecretRepository secretRepository, IProjectRepository projectRepository, ICreateSecretCommand createSecretCommand, IUpdateSecretCommand updateSecretCommand, IDeleteSecretCommand deleteSecretCommand, IUserService userService, ICurrentContext currentContext) { _currentContext = currentContext; _secretRepository = secretRepository; _createSecretCommand = createSecretCommand; _updateSecretCommand = updateSecretCommand; _deleteSecretCommand = deleteSecretCommand; + _projectRepository = projectRepository; + _userService = userService; } [HttpGet("organizations/{organizationId}/secrets")] @@ -42,7 +44,12 @@ public class SecretsController : Controller throw new NotFoundException(); } - var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId); + var userId = _userService.GetProperUserId(User).Value; + var orgAdmin = await _currentContext.OrganizationAdmin(organizationId); + var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + + var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId, userId, accessClient); + return new SecretWithProjectsListResponseModel(secrets); } @@ -54,7 +61,8 @@ public class SecretsController : Controller throw new NotFoundException(); } - var result = await _createSecretCommand.CreateAsync(createRequest.ToSecret(organizationId)); + var userId = _userService.GetProperUserId(User).Value; + var result = await _createSecretCommand.CreateAsync(createRequest.ToSecret(organizationId), userId); return new SecretResponseModel(result); } @@ -62,34 +70,74 @@ public class SecretsController : Controller public async Task GetAsync([FromRoute] Guid id) { var secret = await _secretRepository.GetByIdAsync(id); - if (secret == null) + if (secret == null || !_currentContext.AccessSecretsManager(secret.OrganizationId)) { throw new NotFoundException(); } + + if (!await UserHasReadAccessToSecret(secret)) + { + throw new NotFoundException(); + } + return new SecretResponseModel(secret); } [HttpGet("projects/{projectId}/secrets")] public async Task GetSecretsByProjectAsync([FromRoute] Guid projectId) { - var secrets = await _secretRepository.GetManyByProjectIdAsync(projectId); - var responses = secrets.Select(s => new SecretResponseModel(s)); + var project = await _projectRepository.GetByIdAsync(projectId); + if (project == null || !_currentContext.AccessSecretsManager(project.OrganizationId)) + { + throw new NotFoundException(); + } + + var userId = _userService.GetProperUserId(User).Value; + var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId); + var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + + var secrets = await _secretRepository.GetManyByProjectIdAsync(projectId, userId, accessClient); + return new SecretWithProjectsListResponseModel(secrets); } [HttpPut("secrets/{id}")] - public async Task UpdateAsync([FromRoute] Guid id, [FromBody] SecretUpdateRequestModel updateRequest) + public async Task UpdateSecretAsync([FromRoute] Guid id, [FromBody] SecretUpdateRequestModel updateRequest) { - var result = await _updateSecretCommand.UpdateAsync(updateRequest.ToSecret(id)); + var userId = _userService.GetProperUserId(User).Value; + var secret = updateRequest.ToSecret(id); + var result = await _updateSecretCommand.UpdateAsync(secret, userId); return new SecretResponseModel(result); } - // TODO Once permissions are setup for Secrets Manager need to enforce them on delete. [HttpPost("secrets/delete")] public async Task> BulkDeleteAsync([FromBody] List ids) { - var results = await _deleteSecretCommand.DeleteSecrets(ids); + var userId = _userService.GetProperUserId(User).Value; + var results = await _deleteSecretCommand.DeleteSecrets(ids, userId); var responses = results.Select(r => new BulkDeleteResponseModel(r.Item1.Id, r.Item2)); return new ListResponseModel(responses); } + + public async Task UserHasReadAccessToSecret(Secret secret) + { + var userId = _userService.GetProperUserId(User).Value; + var orgAdmin = await _currentContext.OrganizationAdmin(secret.OrganizationId); + var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); + var hasAccess = orgAdmin; + + if (secret.Projects?.Count > 0) + { + Guid projectId = secret.Projects.FirstOrDefault().Id; + hasAccess = accessClient switch + { + AccessClientType.NoAccessCheck => true, + AccessClientType.User => await _projectRepository.UserHasReadAccessToProject(projectId, userId), + AccessClientType.ServiceAccount => await _projectRepository.ServiceAccountHasReadAccessToProject(projectId, userId), + _ => false, + }; + } + + return hasAccess; + } } diff --git a/src/Api/SecretsManager/Controllers/SecretsManagerPortingController.cs b/src/Api/SecretsManager/Controllers/SecretsManagerPortingController.cs index e85ace3b61..d1e75a328b 100644 --- a/src/Api/SecretsManager/Controllers/SecretsManagerPortingController.cs +++ b/src/Api/SecretsManager/Controllers/SecretsManagerPortingController.cs @@ -38,7 +38,7 @@ public class SecretsManagerPortingController : Controller var userId = _userService.GetProperUserId(User).Value; var projects = await _projectRepository.GetManyByOrganizationIdAsync(organizationId, userId, AccessClientType.NoAccessCheck); - var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId); + var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId, userId, AccessClientType.NoAccessCheck); if (projects == null && secrets == null) { diff --git a/src/Core/SecretsManager/Commands/Secrets/Interfaces/ICreateSecretCommand.cs b/src/Core/SecretsManager/Commands/Secrets/Interfaces/ICreateSecretCommand.cs index 9757346175..2fad757847 100644 --- a/src/Core/SecretsManager/Commands/Secrets/Interfaces/ICreateSecretCommand.cs +++ b/src/Core/SecretsManager/Commands/Secrets/Interfaces/ICreateSecretCommand.cs @@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.Secrets.Interfaces; public interface ICreateSecretCommand { - Task CreateAsync(Secret secret); + Task CreateAsync(Secret secret, Guid userId); } diff --git a/src/Core/SecretsManager/Commands/Secrets/Interfaces/IDeleteSecretCommand.cs b/src/Core/SecretsManager/Commands/Secrets/Interfaces/IDeleteSecretCommand.cs index 493060e52d..2517c70df7 100644 --- a/src/Core/SecretsManager/Commands/Secrets/Interfaces/IDeleteSecretCommand.cs +++ b/src/Core/SecretsManager/Commands/Secrets/Interfaces/IDeleteSecretCommand.cs @@ -4,6 +4,6 @@ namespace Bit.Core.SecretsManager.Commands.Secrets.Interfaces; public interface IDeleteSecretCommand { - Task>> DeleteSecrets(List ids); + Task>> DeleteSecrets(List ids, Guid userId); } diff --git a/src/Core/SecretsManager/Commands/Secrets/Interfaces/IUpdateSecretCommand.cs b/src/Core/SecretsManager/Commands/Secrets/Interfaces/IUpdateSecretCommand.cs index 8c2f61abc0..23e6910c8b 100644 --- a/src/Core/SecretsManager/Commands/Secrets/Interfaces/IUpdateSecretCommand.cs +++ b/src/Core/SecretsManager/Commands/Secrets/Interfaces/IUpdateSecretCommand.cs @@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.Secrets.Interfaces; public interface IUpdateSecretCommand { - Task UpdateAsync(Secret secret); + Task UpdateAsync(Secret secret, Guid userId); } diff --git a/src/Core/SecretsManager/Repositories/IProjectRepository.cs b/src/Core/SecretsManager/Repositories/IProjectRepository.cs index 3d62d571c3..dddd2e21d9 100644 --- a/src/Core/SecretsManager/Repositories/IProjectRepository.cs +++ b/src/Core/SecretsManager/Repositories/IProjectRepository.cs @@ -15,4 +15,6 @@ public interface IProjectRepository Task> ImportAsync(IEnumerable projects); Task UserHasReadAccessToProject(Guid id, Guid userId); Task UserHasWriteAccessToProject(Guid id, Guid userId); + Task ServiceAccountHasWriteAccessToProject(Guid id, Guid userId); + Task ServiceAccountHasReadAccessToProject(Guid id, Guid userId); } diff --git a/src/Core/SecretsManager/Repositories/ISecretRepository.cs b/src/Core/SecretsManager/Repositories/ISecretRepository.cs index 56f4dc8662..8bc9f8eb6e 100644 --- a/src/Core/SecretsManager/Repositories/ISecretRepository.cs +++ b/src/Core/SecretsManager/Repositories/ISecretRepository.cs @@ -1,12 +1,13 @@ -using Bit.Core.SecretsManager.Entities; +using Bit.Core.Enums; +using Bit.Core.SecretsManager.Entities; namespace Bit.Core.SecretsManager.Repositories; public interface ISecretRepository { - Task> GetManyByOrganizationIdAsync(Guid organizationId); + Task> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType); Task> GetManyByIds(IEnumerable ids); - Task> GetManyByProjectIdAsync(Guid projectId); + Task> GetManyByProjectIdAsync(Guid projectId, Guid userId, AccessClientType accessType); Task GetByIdAsync(Guid id); Task CreateAsync(Secret secret); Task UpdateAsync(Secret secret); diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsControllerTest.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsControllerTest.cs index 81ece16d3b..83036a41a6 100644 --- a/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsControllerTest.cs +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsControllerTest.cs @@ -1,9 +1,11 @@ using System.Net; using System.Net.Http.Headers; using Bit.Api.IntegrationTest.Factories; +using Bit.Api.IntegrationTest.SecretsManager.Enums; using Bit.Api.Models.Response; using Bit.Api.SecretsManager.Models.Request; using Bit.Api.SecretsManager.Models.Response; +using Bit.Core.Enums; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; using Bit.Test.Common.Helpers; @@ -20,6 +22,7 @@ public class SecretsControllerTest : IClassFixture, IAsyn private readonly ApiApplicationFactory _factory; private readonly ISecretRepository _secretRepository; private readonly IProjectRepository _projectRepository; + private readonly IAccessPolicyRepository _accessPolicyRepository; private string _email = null!; private SecretsManagerOrganizationHelper _organizationHelper = null!; @@ -30,6 +33,7 @@ public class SecretsControllerTest : IClassFixture, IAsyn _client = _factory.CreateClient(); _secretRepository = _factory.GetService(); _projectRepository = _factory.GetService(); + _accessPolicyRepository = _factory.GetService(); } public async Task InitializeAsync() @@ -64,12 +68,36 @@ public class SecretsControllerTest : IClassFixture, IAsyn Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } - [Fact] - public async Task ListByOrganization_Owner_Success() + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task ListByOrganization_Success(PermissionType permissionType) { - var (org, _) = await _organizationHelper.Initialize(true, true); + var (org, orgUserOwner) = await _organizationHelper.Initialize(true, true); await LoginAsync(_email); + var project = await _projectRepository.CreateAsync(new Project + { + Id = new Guid(), + OrganizationId = org.Id, + Name = _mockEncryptedString, + }); + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var accessPolicies = new List + { + new UserProjectAccessPolicy + { + GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + var secretIds = new List(); for (var i = 0; i < 3; i++) { @@ -78,7 +106,9 @@ public class SecretsControllerTest : IClassFixture, IAsyn OrganizationId = org.Id, Key = _mockEncryptedString, Value = _mockEncryptedString, - Note = _mockEncryptedString + Note = _mockEncryptedString, + Projects = new List { project } + }); secretIds.Add(secret.Id); } @@ -113,7 +143,7 @@ public class SecretsControllerTest : IClassFixture, IAsyn } [Fact] - public async Task Create_Owner_Success() + public async Task CreateWithoutProject_RunAsAdmin_Success() { var (org, _) = await _organizationHelper.Initialize(true, true); await LoginAsync(_email); @@ -147,11 +177,33 @@ public class SecretsControllerTest : IClassFixture, IAsyn } [Fact] - public async Task CreateWithProject_Owner_Success() + public async Task CreateWithoutProject_RunAsUser_NotFound() { var (org, _) = await _organizationHelper.Initialize(true, true); + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var request = new SecretCreateRequestModel + { + Key = _mockEncryptedString, + Value = _mockEncryptedString, + Note = _mockEncryptedString + }; + + var response = await _client.PostAsJsonAsync($"/organizations/{org.Id}/secrets", request); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task CreateWithProject_Success(PermissionType permissionType) + { + var (org, orgAdminUser) = await _organizationHelper.Initialize(true, true); await LoginAsync(_email); + AccessClientType accessType = AccessClientType.NoAccessCheck; + var project = await _projectRepository.CreateAsync(new Project() { Id = new Guid(), @@ -159,6 +211,25 @@ public class SecretsControllerTest : IClassFixture, IAsyn Name = _mockEncryptedString }); + var orgUserId = (Guid)orgAdminUser.UserId; + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + accessType = AccessClientType.User; + + var accessPolicies = new List + { + new Core.SecretsManager.Entities.UserProjectAccessPolicy + { + GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id , Read = true, Write = true, + }, + }; + orgUserId = (Guid)orgUser.UserId; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + var secretRequest = new SecretCreateRequestModel() { Key = _mockEncryptedString, @@ -170,7 +241,7 @@ public class SecretsControllerTest : IClassFixture, IAsyn secretResponse.EnsureSuccessStatusCode(); var secretResult = await secretResponse.Content.ReadFromJsonAsync(); - var secret = (await _secretRepository.GetManyByProjectIdAsync(project.Id)).First(); + var secret = (await _secretRepository.GetManyByProjectIdAsync(project.Id, orgUserId, accessType)).First(); Assert.NotNull(secretResult); Assert.Equal(secret.Id.ToString(), secretResult!.Id); @@ -203,18 +274,48 @@ public class SecretsControllerTest : IClassFixture, IAsyn Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } - [Fact] - public async Task Get_Owner_Success() + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task Get_Success(PermissionType permissionType) { var (org, _) = await _organizationHelper.Initialize(true, true); await LoginAsync(_email); + var project = await _projectRepository.CreateAsync(new Project() + { + Id = new Guid(), + OrganizationId = org.Id, + Name = _mockEncryptedString + }); + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var accessPolicies = new List + { + new UserProjectAccessPolicy + { + GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + else + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.Admin, true); + await LoginAsync(email); + } + var secret = await _secretRepository.CreateAsync(new Secret { OrganizationId = org.Id, Key = _mockEncryptedString, Value = _mockEncryptedString, - Note = _mockEncryptedString + Note = _mockEncryptedString, + Projects = new List { project } }); var response = await _client.GetAsync($"/secrets/{secret.Id}"); @@ -255,25 +356,51 @@ public class SecretsControllerTest : IClassFixture, IAsyn Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } - [Fact] - public async Task Update_Owner_Success() + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task Update_Success(PermissionType permissionType) { var (org, _) = await _organizationHelper.Initialize(true, true); await LoginAsync(_email); + var project = await _projectRepository.CreateAsync(new Project() + { + Id = new Guid(), + OrganizationId = org.Id, + Name = _mockEncryptedString + }); + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var accessPolicies = new List + { + new UserProjectAccessPolicy + { + GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + var secret = await _secretRepository.CreateAsync(new Secret { OrganizationId = org.Id, Key = _mockEncryptedString, Value = _mockEncryptedString, - Note = _mockEncryptedString + Note = _mockEncryptedString, + Projects = permissionType == PermissionType.RunAsUserWithPermission ? new List() { project } : null }); var request = new SecretUpdateRequestModel() { Key = _mockEncryptedString, Value = "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98xy4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg=", - Note = _mockEncryptedString + Note = _mockEncryptedString, + ProjectIds = permissionType == PermissionType.RunAsUserWithPermission ? new Guid[] { project.Id } : null }; var response = await _client.PutAsJsonAsync($"/secrets/{secret.Id}", request); @@ -316,16 +443,41 @@ public class SecretsControllerTest : IClassFixture, IAsyn }); var secretIds = new[] { secret.Id }; - var response = await _client.PostAsJsonAsync("/secrets/delete", secretIds); + var response = await _client.PostAsJsonAsync($"/secrets/{org.Id}/delete", secretIds); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } - [Fact] - public async Task Delete_Owner_Success() + [Theory] + [InlineData(PermissionType.RunAsAdmin)] + [InlineData(PermissionType.RunAsUserWithPermission)] + public async Task Delete_Success(PermissionType permissionType) { var (org, _) = await _organizationHelper.Initialize(true, true); await LoginAsync(_email); + var project = await _projectRepository.CreateAsync(new Project() + { + Id = new Guid(), + OrganizationId = org.Id, + Name = _mockEncryptedString + }); + + if (permissionType == PermissionType.RunAsUserWithPermission) + { + var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var accessPolicies = new List + { + new UserProjectAccessPolicy + { + GrantedProjectId = project.Id, OrganizationUserId = orgUser.Id, Read = true, Write = true, + }, + }; + await _accessPolicyRepository.CreateManyAsync(accessPolicies); + } + + var secretIds = new List(); for (var i = 0; i < 3; i++) { @@ -334,12 +486,13 @@ public class SecretsControllerTest : IClassFixture, IAsyn OrganizationId = org.Id, Key = _mockEncryptedString, Value = _mockEncryptedString, - Note = _mockEncryptedString + Note = _mockEncryptedString, + Projects = new List() { project } }); secretIds.Add(secret.Id); } - var response = await _client.PostAsJsonAsync("/secrets/delete", secretIds); + var response = await _client.PostAsJsonAsync($"/secrets/delete", secretIds); response.EnsureSuccessStatusCode(); var results = await response.Content.ReadFromJsonAsync>(); diff --git a/test/Api.Test/SecretsManager/Controllers/SecretsControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/SecretsControllerTests.cs index aff105abed..ab020f7514 100644 --- a/test/Api.Test/SecretsManager/Controllers/SecretsControllerTests.cs +++ b/test/Api.Test/SecretsManager/Controllers/SecretsControllerTests.cs @@ -1,10 +1,13 @@ using Bit.Api.SecretsManager.Controllers; using Bit.Api.SecretsManager.Models.Request; +using Bit.Api.Test.SecretsManager.Enums; using Bit.Core.Context; +using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.SecretsManager.Commands.Secrets.Interfaces; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Services; using Bit.Core.Test.SecretsManager.AutoFixture.SecretsFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -22,33 +25,50 @@ public class SecretsControllerTests { [Theory] [BitAutoData] - public async void GetSecretsByOrganization_ReturnsEmptyList(SutProvider sutProvider, Guid id) + public async void GetSecretsByOrganization_ReturnsEmptyList(SutProvider sutProvider, Guid id, Guid organizationId, Guid userId, AccessClientType accessType) { sutProvider.GetDependency().AccessSecretsManager(id).Returns(true); + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(true); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(userId); + var result = await sutProvider.Sut.ListByOrganizationAsync(id); await sutProvider.GetDependency().Received(1) - .GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id))); + .GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(id)), userId, accessType); Assert.Empty(result.Secrets); } [Theory] - [BitAutoData] - public async void GetSecretsByOrganization_Success(SutProvider sutProvider, Secret resultSecret) + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void GetSecretsByOrganization_Success(PermissionType permissionType, SutProvider sutProvider, Core.SecretsManager.Entities.Secret resultSecret, Guid organizationId, Guid userId, Core.SecretsManager.Entities.Project mockProject, AccessClientType accessType) { sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(true); - sutProvider.GetDependency().GetManyByOrganizationIdAsync(default).ReturnsForAnyArgs(new List { resultSecret }); + sutProvider.GetDependency().GetManyByOrganizationIdAsync(default, default, default).ReturnsForAnyArgs(new List { resultSecret }); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(userId); + + if (permissionType == PermissionType.RunAsAdmin) + { + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(true); + } + else + { + resultSecret.Projects = new List() { mockProject }; + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(false); + sutProvider.GetDependency().UserHasReadAccessToProject(mockProject.Id, userId).Returns(true); + } + var result = await sutProvider.Sut.ListByOrganizationAsync(resultSecret.OrganizationId); await sutProvider.GetDependency().Received(1) - .GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(resultSecret.OrganizationId))); + .GetManyByOrganizationIdAsync(Arg.Is(AssertHelper.AssertPropertyEqual(resultSecret.OrganizationId)), userId, accessType); } [Theory] [BitAutoData] - public async void GetSecretsByOrganization_AccessDenied_Throws(SutProvider sutProvider, Secret resultSecret) + public async void GetSecretsByOrganization_AccessDenied_Throws(SutProvider sutProvider, Core.SecretsManager.Entities.Secret resultSecret) { sutProvider.GetDependency().AccessSecretsManager(default).ReturnsForAnyArgs(false); @@ -64,11 +84,29 @@ public class SecretsControllerTests } [Theory] - [BitAutoData] - public async void GetSecret_Success(SutProvider sutProvider, Secret resultSecret) + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void GetSecret_Success(PermissionType permissionType, SutProvider sutProvider, Secret resultSecret, Guid userId, Guid organizationId, Project mockProject) { + sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(userId); + mockProject.OrganizationId = organizationId; + resultSecret.Projects = new List() { mockProject }; + resultSecret.OrganizationId = organizationId; + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(resultSecret); + if (permissionType == PermissionType.RunAsAdmin) + { + resultSecret.OrganizationId = organizationId; + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(true); + } + else + { + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(false); + sutProvider.GetDependency().UserHasReadAccessToProject(mockProject.Id, userId).Returns(true); + } + var result = await sutProvider.Sut.GetAsync(resultSecret.Id); await sutProvider.GetDependency().Received(1) @@ -76,46 +114,89 @@ public class SecretsControllerTests } [Theory] - [BitAutoData] - public async void CreateSecret_Success(SutProvider sutProvider, SecretCreateRequestModel data, Guid organizationId) + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void CreateSecret_Success(PermissionType permissionType, SutProvider sutProvider, SecretCreateRequestModel data, Guid organizationId, Project mockProject, Guid userId) { var resultSecret = data.ToSecret(organizationId); + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(userId); + + if (permissionType == PermissionType.RunAsAdmin) + { + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(true); + } + else + { + resultSecret.Projects = new List() { mockProject }; + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(false); + sutProvider.GetDependency().UserHasReadAccessToProject(mockProject.Id, userId).Returns(true); + } sutProvider.GetDependency().AccessSecretsManager(organizationId).Returns(true); - sutProvider.GetDependency().CreateAsync(default).ReturnsForAnyArgs(resultSecret); + sutProvider.GetDependency().CreateAsync(default, userId).ReturnsForAnyArgs(resultSecret); var result = await sutProvider.Sut.CreateAsync(organizationId, data); await sutProvider.GetDependency().Received(1) - .CreateAsync(Arg.Any()); + .CreateAsync(Arg.Any(), userId); } [Theory] - [BitAutoData] - public async void UpdateSecret_Success(SutProvider sutProvider, SecretUpdateRequestModel data, Guid secretId) + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void UpdateSecret_Success(PermissionType permissionType, SutProvider sutProvider, SecretUpdateRequestModel data, Guid secretId, Guid organizationId, Guid userId, Project mockProject) { + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(userId); + + if (permissionType == PermissionType.RunAsAdmin) + { + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(true); + } + else + { + data.ProjectIds = new Guid[] { mockProject.Id }; + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(false); + sutProvider.GetDependency().UserHasReadAccessToProject(mockProject.Id, userId).Returns(true); + } + var resultSecret = data.ToSecret(secretId); - sutProvider.GetDependency().UpdateAsync(default).ReturnsForAnyArgs(resultSecret); + sutProvider.GetDependency().UpdateAsync(default, userId).ReturnsForAnyArgs(resultSecret); - var result = await sutProvider.Sut.UpdateAsync(secretId, data); + var result = await sutProvider.Sut.UpdateSecretAsync(secretId, data); await sutProvider.GetDependency().Received(1) - .UpdateAsync(Arg.Any()); + .UpdateAsync(Arg.Any(), userId); } [Theory] - [BitAutoData] - public async void BulkDeleteSecret_Success(SutProvider sutProvider, List data) + [BitAutoData(PermissionType.RunAsAdmin)] + [BitAutoData(PermissionType.RunAsUserWithPermission)] + public async void BulkDeleteSecret_Success(PermissionType permissionType, SutProvider sutProvider, List data, Guid organizationId, Guid userId, Project mockProject) { + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(userId); + + if (permissionType == PermissionType.RunAsAdmin) + { + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(true); + } + else + { + data.FirstOrDefault().Projects = new List() { mockProject }; + sutProvider.GetDependency().OrganizationAdmin(organizationId).Returns(false); + sutProvider.GetDependency().UserHasReadAccessToProject(mockProject.Id, userId).Returns(true); + } + + var ids = data.Select(secret => secret.Id).ToList(); var mockResult = new List>(); + foreach (var secret in data) { mockResult.Add(new Tuple(secret, "")); } - sutProvider.GetDependency().DeleteSecrets(ids).ReturnsForAnyArgs(mockResult); + sutProvider.GetDependency().DeleteSecrets(ids, userId).ReturnsForAnyArgs(mockResult); var results = await sutProvider.Sut.BulkDeleteAsync(ids); await sutProvider.GetDependency().Received(1) - .DeleteSecrets(Arg.Is(ids)); + .DeleteSecrets(Arg.Is(ids), userId); Assert.Equal(data.Count, results.Data.Count()); } @@ -123,6 +204,7 @@ public class SecretsControllerTests [BitAutoData] public async void BulkDeleteSecret_NoGuids_ThrowsArgumentNullException(SutProvider sutProvider) { + sutProvider.GetDependency().GetProperUserId(default).ReturnsForAnyArgs(new Guid()); await Assert.ThrowsAsync(() => sutProvider.Sut.BulkDeleteAsync(new List())); } } From 1bbdafbe7a88be7487788c419b30aa6b9dc3f206 Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Thu, 16 Feb 2023 16:26:44 -0500 Subject: [PATCH 30/49] Updating Org Domain events to match existing pattern of incrementing event type enum by 100 to group new types of events. (#2710) --- src/Core/Enums/EventType.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Core/Enums/EventType.cs b/src/Core/Enums/EventType.cs index da3b64fdb5..77e62ed3ad 100644 --- a/src/Core/Enums/EventType.cs +++ b/src/Core/Enums/EventType.cs @@ -1,5 +1,6 @@ namespace Bit.Core.Enums; +// Increment by 100 for each new set of events public enum EventType : int { User_LoggedIn = 1000, @@ -76,8 +77,8 @@ public enum EventType : int ProviderOrganization_Removed = 1902, ProviderOrganization_VaultAccessed = 1903, - OrganizationDomain_Added = 1904, - OrganizationDomain_Removed = 1905, - OrganizationDomain_Verified = 1906, - OrganizationDomain_NotVerified = 1907 + OrganizationDomain_Added = 2000, + OrganizationDomain_Removed = 2001, + OrganizationDomain_Verified = 2002, + OrganizationDomain_NotVerified = 2003 } From 0fde17fc0ed3ef2d73a58717f134e6d4960699d0 Mon Sep 17 00:00:00 2001 From: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Date: Thu, 16 Feb 2023 14:16:32 -0800 Subject: [PATCH 31/49] Add in QA temporary ACR (#2711) * Adding QA registry back into self-host build pipeline * switching order of the ACR signin * Update build pipeline to follow same patterns as build-self-host and push to both Prod and QA registries * Add Bitwarden QA registry to the PR clean up workflow * Fix project name and path to dockerfile * Add a publish branch check to the tag list generator * Fix bash env var typo --- .github/workflows/build-self-host.yml | 15 +- .github/workflows/build.yml | 222 ++++++++++++------------- .github/workflows/cleanup-after-pr.yml | 42 +++-- 3 files changed, 149 insertions(+), 130 deletions(-) diff --git a/.github/workflows/build-self-host.yml b/.github/workflows/build-self-host.yml index 8d2a6ec498..9050ce333a 100644 --- a/.github/workflows/build-self-host.yml +++ b/.github/workflows/build-self-host.yml @@ -45,7 +45,15 @@ jobs: uses: docker/setup-buildx-action@8c0edbc76e98fa90f69d9a2c020dcb50019dc325 ########## Login to Docker registries ########## - - name: Login to Azure - PROD Subscription + - name: Login to Azure - QA Subscription + uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf + with: + creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }} + + - name: Login to Azure ACR + run: az acr login -n bitwardenqa + + - name: Login to Azure - Prod Subscription uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} @@ -53,6 +61,7 @@ jobs: - name: Login to Azure ACR run: az acr login -n bitwardenprod + - name: Retrieve github PAT secrets id: retrieve-secret-pat uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af @@ -109,9 +118,9 @@ jobs: IMAGE_TAG: ${{ steps.tag.outputs.image_tag }} run: | if [ "$IMAGE_TAG" = "dev" ] || [ "$IMAGE_TAG" = "beta" ]; then - echo "tags=bitwardenprod.azurecr.io/self-host:${IMAGE_TAG},bitwarden/self-host:${IMAGE_TAG}" >> $GITHUB_OUTPUT + echo "tags=bitwardenqa.azurecr.io/self-host:${IMAGE_TAG},bitwardenprod.azurecr.io/self-host:${IMAGE_TAG},bitwarden/self-host:${IMAGE_TAG}" >> $GITHUB_OUTPUT else - echo "tags=bitwardenprod.azurecr.io/self-host:${IMAGE_TAG}" >> $GITHUB_OUTPUT + echo "tags=bitwardenqa.azurecr.io/self-host:${IMAGE_TAG},bitwardenprod.azurecr.io/self-host:${IMAGE_TAG}" >> $GITHUB_OUTPUT fi - name: Build Docker image diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bc2cec89d1..24cc0097bf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -191,74 +191,145 @@ jobs: include: - project_name: Admin base_path: ./src - docker_repos: [bitwarden, bitwardenprod.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io] dotnet: true - project_name: Api base_path: ./src - docker_repos: [bitwarden, bitwardenprod.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io] dotnet: true - project_name: Attachments base_path: ./util - docker_repos: [bitwarden, bitwardenprod.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io] - project_name: Events base_path: ./src - docker_repos: [bitwarden, bitwardenprod.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io] dotnet: true - project_name: EventsProcessor base_path: ./src - docker_repos: [bitwardenprod.azurecr.io] + docker_repos: [bitwardenprod.azurecr.io, bitwardenqa.azurecr.io] dotnet: true - project_name: Icons base_path: ./src - docker_repos: [bitwarden, bitwardenprod.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io] dotnet: true - project_name: Identity base_path: ./src - docker_repos: [bitwarden, bitwardenprod.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io] dotnet: true - project_name: MsSql base_path: ./util - docker_repos: [bitwarden, bitwardenprod.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io] - project_name: Nginx base_path: ./util - docker_repos: [bitwarden, bitwardenprod.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io] - project_name: Notifications base_path: ./src - docker_repos: [bitwarden, bitwardenprod.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io] dotnet: true - project_name: Server base_path: ./util - docker_repos: [bitwarden, bitwardenprod.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io] dotnet: true - project_name: Setup base_path: ./util - docker_repos: [bitwarden, bitwardenprod.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io] dotnet: true - project_name: Sso base_path: ./bitwarden_license/src - docker_repos: [bitwarden, bitwardenprod.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io] dotnet: true - project_name: Scim base_path: ./bitwarden_license/src - docker_repos: [bitwarden, bitwardenprod.azurecr.io] + docker_repos: [bitwarden, bitwardenprod.azurecr.io, bitwardenqa.azurecr.io] dotnet: true - project_name: Billing base_path: ./src - docker_repos: [bitwardenprod.azurecr.io] + docker_repos: [bitwardenprod.azurecr.io, bitwardenqa.azurecr.io] dotnet: true steps: - name: Checkout repo uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 - - name: Set up image tag + - name: Check Branch to Publish + env: + PUBLISH_BRANCHES: "master,rc,hotfix-rc" + id: publish-branch-check + run: | + IFS="," read -a publish_branches <<< $PUBLISH_BRANCHES + + if [[ " ${publish_branches[*]} " =~ " ${GITHUB_REF:11} " ]]; then + echo "is_publish_branch=true" >> $GITHUB_ENV + else + echo "is_publish_branch=false" >> $GITHUB_ENV + fi + + ########## ACRs ########## + - name: Login to Azure - QA Subscription + uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf + with: + creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }} + + - name: Login to QA ACR + run: az acr login -n bitwardenqa + + - name: Login to Azure - PROD Subscription + uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf + with: + creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} + + - name: Login to PROD ACR + run: az acr login -n bitwardenprod + + - name: Retrieve github PAT secrets + id: retrieve-secret-pat + uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af + with: + keyvault: "bitwarden-prod-kv" + secrets: "github-pat-bitwarden-devops-bot-repo-scope" + + - name: Retrieve secrets + if: ${{ env.is_publish_branch == 'true' }} + id: retrieve-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af + with: + keyvault: "bitwarden-prod-kv" + secrets: "docker-password, + docker-username, + dct-delegate-2-repo-passphrase, + dct-delegate-2-key" + + - name: Log into Docker + if: ${{ env.is_publish_branch == 'true' }} + env: + DOCKER_USERNAME: ${{ steps.retrieve-secrets.outputs.docker-username }} + DOCKER_PASSWORD: ${{ steps.retrieve-secrets.outputs.docker-password }} + run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin + + - name: Setup Docker Trust + if: ${{ env.is_publish_branch == 'true' }} + env: + DCT_DELEGATION_KEY_ID: "c9bde8ec820701516491e5e03d3a6354e7bd66d05fa3df2b0062f68b116dc59c" + DCT_DELEGATE_KEY: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-key }} + DCT_REPO_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }} + run: | + mkdir -p ~/.docker/trust/private + echo "$DCT_DELEGATE_KEY" > ~/.docker/trust/private/$DCT_DELEGATION_KEY_ID.key + echo "DOCKER_CONTENT_TRUST=1" >> $GITHUB_ENV + echo "DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE=$DCT_REPO_PASSPHRASE" >> $GITHUB_ENV + + ########## Generate image tag and build Docker image ########## + - name: Generate Docker image tag + id: tag run: | IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name if [[ "$IMAGE_TAG" == "master" ]]; then IMAGE_TAG=dev + elif [[ "$IMAGE_TAG" == "rc" ]] || [[ "$IMAGE_TAG" == "hotfix-rc" ]]; then + IMAGE_TAG=beta fi - echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV - ########## Build Docker Image ########## + echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT + - name: Setup project name id: setup run: | @@ -267,6 +338,18 @@ jobs: echo "PROJECT_NAME: $PROJECT_NAME" echo "project_name=$PROJECT_NAME" >> $GITHUB_OUTPUT + - name: Generate tag list + id: tag-list + env: + IMAGE_TAG: ${{ steps.tag.outputs.image_tag }} + PROJECT_NAME: ${{ steps.setup.outputs.project_name }} + run: | + if [ "${{ env.is_publish_branch }}" == "true" ]; then + echo "tags=bitwardenqa.azurecr.io/${PROJECT_NAME}:${IMAGE_TAG},bitwardenprod.azurecr.io/${PROJECT_NAME}:${IMAGE_TAG},bitwarden/${PROJECT_NAME}:${IMAGE_TAG}" >> $GITHUB_OUTPUT + else + echo "tags=bitwardenqa.azurecr.io/${PROJECT_NAME}:${IMAGE_TAG},bitwardenprod.azurecr.io/${PROJECT_NAME}:${IMAGE_TAG}" >> $GITHUB_OUTPUT + fi + - name: Get build artifact if: ${{ matrix.dotnet }} uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741 @@ -281,104 +364,17 @@ jobs: -d ${{ matrix.base_path }}/${{ matrix.project_name }}/obj/build-output/publish - name: Build Docker image - env: - PROJECT_NAME: ${{ steps.setup.outputs.project_name }} - run: docker build -t $PROJECT_NAME ${{ matrix.base_path }}/${{ matrix.project_name }} - - ########## PROD ACR ########## - - name: Login to Azure - PROD Subscription - uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf + uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5 with: - creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} - - - name: Login to PROD ACR - run: az acr login -n bitwardenprod - - - name: Tag and push image to PROD ACR - env: - PROJECT_NAME: ${{ steps.setup.outputs.project_name }} - REGISTRY: bitwardenprod.azurecr.io - run: | - docker tag $PROJECT_NAME \ - $REGISTRY/$PROJECT_NAME:${{ env.IMAGE_TAG }} - docker push $REGISTRY/$PROJECT_NAME:${{ env.IMAGE_TAG }} + context: ${{ matrix.base_path }}/${{ matrix.project_name }} + file: ${{ matrix.base_path }}/${{ matrix.project_name }}/Dockerfile + platforms: linux/amd64 + push: true + tags: ${{ steps.tag-list.outputs.tags }} + secrets: | + "GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}" - name: Log out of Docker - run: docker logout - - ########## DockerHub ########## - - name: Login to Azure - Prod Subscription - if: | - contains(matrix.docker_repos, 'bitwarden') - && (github.ref == 'refs/heads/master' || - github.ref == 'refs/heads/rc' || - github.ref == 'refs/heads/hotfix-rc') - uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf - with: - creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} - - - name: Retrieve secrets - if: | - contains(matrix.docker_repos, 'bitwarden') - && (github.ref == 'refs/heads/master' || - github.ref == 'refs/heads/rc' || - github.ref == 'refs/heads/hotfix-rc') - id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af - with: - keyvault: "bitwarden-prod-kv" - secrets: "docker-password, - docker-username, - dct-delegate-2-repo-passphrase, - dct-delegate-2-key" - - - name: Log into Docker - if: | - contains(matrix.docker_repos, 'bitwarden') - && (github.ref == 'refs/heads/master' || - github.ref == 'refs/heads/rc' || - github.ref == 'refs/heads/hotfix-rc') - env: - DOCKER_USERNAME: ${{ steps.retrieve-secrets.outputs.docker-username }} - DOCKER_PASSWORD: ${{ steps.retrieve-secrets.outputs.docker-password }} - run: echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin - - - name: Setup Docker Trust - if: | - contains(matrix.docker_repos, 'bitwarden') - && (github.ref == 'refs/heads/master' || - github.ref == 'refs/heads/rc' || - github.ref == 'refs/heads/hotfix-rc') - env: - DCT_DELEGATION_KEY_ID: "c9bde8ec820701516491e5e03d3a6354e7bd66d05fa3df2b0062f68b116dc59c" - DCT_DELEGATE_KEY: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-key }} - DCT_REPO_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }} - run: | - mkdir -p ~/.docker/trust/private - echo "$DCT_DELEGATE_KEY" > ~/.docker/trust/private/$DCT_DELEGATION_KEY_ID.key - echo "DOCKER_CONTENT_TRUST=1" >> $GITHUB_ENV - echo "DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE=$DCT_REPO_PASSPHRASE" >> $GITHUB_ENV - - - name: Tag and Push RC to Docker Hub - if: | - contains(matrix.docker_repos, 'bitwarden') - && (github.ref == 'refs/heads/master' || - github.ref == 'refs/heads/rc' || - github.ref == 'refs/heads/hotfix-rc') - env: - PROJECT_NAME: ${{ steps.setup.outputs.project_name }} - REGISTRY: bitwarden - run: | - docker tag $PROJECT_NAME \ - $REGISTRY/$PROJECT_NAME:${{ env.IMAGE_TAG }} - docker push $REGISTRY/$PROJECT_NAME:${{ env.IMAGE_TAG }} - - - name: Log out of Docker and disable Docker Notary - if: | - contains(matrix.docker_repos, 'bitwarden') - && (github.ref == 'refs/heads/master' || - github.ref == 'refs/heads/rc' || - github.ref == 'refs/heads/hotfix-rc') run: | docker logout echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV diff --git a/.github/workflows/cleanup-after-pr.yml b/.github/workflows/cleanup-after-pr.yml index 5fc34700fb..c1eca91079 100644 --- a/.github/workflows/cleanup-after-pr.yml +++ b/.github/workflows/cleanup-after-pr.yml @@ -14,6 +14,14 @@ jobs: uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f ########## ACR ########## + - name: Login to Azure - QA Subscription + uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a + with: + creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }} + + - name: Login to Azure ACR + run: az acr login -n bitwardenqa + k - name: Login to Azure - PROD Subscription uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a with: @@ -25,7 +33,10 @@ jobs: ########## Remove Docker images ########## - name: Remove the docker image from ACR env: - REGISTRY_NAME: bitwardenprod + REGISTRIES: | + registries: + - bitwardenprod + - bitwardenqa SERVICES: | services: - Admin @@ -45,21 +56,24 @@ jobs: run: | for SERVICE in $(echo "${{ env.SERVICES }}" | yq e ".services[]" - ) do - SERVICE_NAME=$(echo $SERVICE | awk '{print tolower($0)}') - IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name + for REGISTRY in $( echo "${{ env.REGISTRIES }}" | yq e ".registries[]" - ) + do + SERVICE_NAME=$(echo $SERVICE | awk '{print tolower($0)}') + IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name - echo "[*] Checking if remote exists: $REGISTRY_NAME.azurecr.io/$SERVICE_NAME:$IMAGE_TAG" - TAG_EXISTS=$( - az acr repository show-tags --name $REGISTRY_NAME --repository $SERVICE_NAME \ - | jq --arg $TAG "$IMAGE_TAG" -e '. | any(. == "$TAG")' - ) + echo "[*] Checking if remote exists: $REGISTRY.azurecr.io/$SERVICE_NAME:$IMAGE_TAG" + TAG_EXISTS=$( + az acr repository show-tags --name $REGISTRY --repository $SERVICE_NAME \ + | jq --arg $TAG "$IMAGE_TAG" -e '. | any(. == "$TAG")' + ) - if [[ "$TAG_EXISTS" == "true" ]]; then - echo "[*] Tag exists. Removing tag" - az acr repository delete --name $REGISTRY_NAME --image $SERVICE_NAME:$IMAGE_TAG --yes - else - echo "[*] Tag does not exist. No action needed" - fi + if [[ "$TAG_EXISTS" == "true" ]]; then + echo "[*] Tag exists. Removing tag" + az acr repository delete --name $REGISTRY --image $SERVICE_NAME:$IMAGE_TAG --yes + else + echo "[*] Tag does not exist. No action needed" + fi + done done - name: Log out of Docker From 133a3f70b921ff939e6550f4c48824623ffc3964 Mon Sep 17 00:00:00 2001 From: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Date: Thu, 16 Feb 2023 15:14:39 -0800 Subject: [PATCH 32/49] Don't upload images to DockerHub registries that don't exist (#2712) * Don't upload images to DockerHub registries that don't exist * Fix linting issue * Fix conditional --- .github/workflows/build.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 24cc0097bf..716eec9f69 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -343,12 +343,7 @@ jobs: env: IMAGE_TAG: ${{ steps.tag.outputs.image_tag }} PROJECT_NAME: ${{ steps.setup.outputs.project_name }} - run: | - if [ "${{ env.is_publish_branch }}" == "true" ]; then - echo "tags=bitwardenqa.azurecr.io/${PROJECT_NAME}:${IMAGE_TAG},bitwardenprod.azurecr.io/${PROJECT_NAME}:${IMAGE_TAG},bitwarden/${PROJECT_NAME}:${IMAGE_TAG}" >> $GITHUB_OUTPUT - else - echo "tags=bitwardenqa.azurecr.io/${PROJECT_NAME}:${IMAGE_TAG},bitwardenprod.azurecr.io/${PROJECT_NAME}:${IMAGE_TAG}" >> $GITHUB_OUTPUT - fi + run: echo "tags=bitwardenqa.azurecr.io/${PROJECT_NAME}:${IMAGE_TAG},bitwardenprod.azurecr.io/${PROJECT_NAME}:${IMAGE_TAG}" >> $GITHUB_OUTPUT - name: Get build artifact if: ${{ matrix.dotnet }} @@ -374,6 +369,15 @@ jobs: secrets: | "GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}" + - name: Push to DockerHub + if: contains(matrix.docker_repos, 'bitwarden') && env.is_publish_branch == 'true' + env: + IMAGE_TAG: ${{ steps.tag.outputs.image_tag }} + run: | + docker tag bitwardenprod.azurecr.io/$PROJECT_NAME:${{ env.IMAGE_TAG }} \ + bitwarden/$PROJECT_NAME:${{ env.IMAGE_TAG }} + docker push bitwarden/$PROJECT_NAME:${{ env.IMAGE_TAG }} + - name: Log out of Docker run: | docker logout From 7594ca1122fda6988a9d8831a8b9b106cd911814 Mon Sep 17 00:00:00 2001 From: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Date: Thu, 16 Feb 2023 15:32:48 -0800 Subject: [PATCH 33/49] Fix typo in the DockerHub push step (#2713) --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 716eec9f69..27de690f80 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -373,10 +373,10 @@ jobs: if: contains(matrix.docker_repos, 'bitwarden') && env.is_publish_branch == 'true' env: IMAGE_TAG: ${{ steps.tag.outputs.image_tag }} + PROJECT_NAME: ${{ steps.setup.outputs.project_name }} run: | - docker tag bitwardenprod.azurecr.io/$PROJECT_NAME:${{ env.IMAGE_TAG }} \ - bitwarden/$PROJECT_NAME:${{ env.IMAGE_TAG }} - docker push bitwarden/$PROJECT_NAME:${{ env.IMAGE_TAG }} + docker tag bitwardenprod.azurecr.io/$PROJECT_NAME:$IMAGE_TAG bitwarden/$PROJECT_NAME:$IMAGE_TAG + docker push bitwarden/$PROJECT_NAME:$IMAGE_TAG - name: Log out of Docker run: | From 69511160cb37dfcc104ae4390330f16b043b2b6b Mon Sep 17 00:00:00 2001 From: Federico Maccaroni Date: Fri, 17 Feb 2023 10:15:28 -0300 Subject: [PATCH 34/49] [EC-400] Code clean up Device Verification (#2601) * EC-400 Clean up code regarding Unknown Device Verification * EC-400 Fix formatting --- src/Api/Controllers/TwoFactorController.cs | 63 +- .../Request/DeviceVerificationRequestModel.cs | 8 +- .../Models/Request/TwoFactorRequestModels.cs | 2 - .../DeviceVerificationResponseModel.cs | 1 + src/Core/Entities/User.cs | 1 - .../NewDeviceLoginTwoFactorEmail.html.hbs | 19 - .../NewDeviceLoginTwoFactorEmail.text.hbs | 7 - src/Core/Services/IMailService.cs | 1 - src/Core/Services/IUserService.cs | 4 +- .../Implementations/EmergencyAccessService.cs | 1 - .../Implementations/HandlebarsMailService.cs | 15 - .../Services/Implementations/UserService.cs | 46 +- .../NoopImplementations/NoopMailService.cs | 5 - src/Core/Settings/GlobalSettings.cs | 6 - src/Core/Settings/IGlobalSettings.cs | 1 - src/Core/Settings/ITwoFactorAuthSettings.cs | 6 - .../IdentityServer/BaseRequestValidator.cs | 72 +- .../Stored Procedures/User_Create.sql | 120 + .../Stored Procedures/User_Update.sql | 82 + src/Sql/dbo_future/Tables/User.sql | 49 + .../Services/EmergencyAccessServiceTests.cs | 4 +- test/Core.Test/Services/UserServiceTests.cs | 224 -- .../2023-01-FutureMigration.sql | 230 ++ ...emoveDeviceUnknownVerification.Designer.cs | 2095 ++++++++++++++++ ...8225349_RemoveDeviceUnknownVerification.cs | 25 + .../DatabaseContextModelSnapshot.cs | 3 - ...emoveDeviceUnknownVerification.Designer.cs | 2106 +++++++++++++++++ ...8224536_RemoveDeviceUnknownVerification.cs | 25 + .../DatabaseContextModelSnapshot.cs | 3 - ...emoveDeviceUnknownVerification.Designer.cs | 2093 ++++++++++++++++ ...8212950_RemoveDeviceUnknownVerification.cs | 25 + .../DatabaseContextModelSnapshot.cs | 3 - 32 files changed, 6876 insertions(+), 469 deletions(-) delete mode 100644 src/Core/MailTemplates/Handlebars/NewDeviceLoginTwoFactorEmail.html.hbs delete mode 100644 src/Core/MailTemplates/Handlebars/NewDeviceLoginTwoFactorEmail.text.hbs delete mode 100644 src/Core/Settings/ITwoFactorAuthSettings.cs create mode 100644 src/Sql/dbo_future/Stored Procedures/User_Create.sql create mode 100644 src/Sql/dbo_future/Stored Procedures/User_Update.sql create mode 100644 src/Sql/dbo_future/Tables/User.sql create mode 100644 util/Migrator/DbScripts_future/2023-01-FutureMigration.sql create mode 100644 util/MySqlMigrations/Migrations/20230118225349_RemoveDeviceUnknownVerification.Designer.cs create mode 100644 util/MySqlMigrations/Migrations/20230118225349_RemoveDeviceUnknownVerification.cs create mode 100644 util/PostgresMigrations/Migrations/20230118224536_RemoveDeviceUnknownVerification.Designer.cs create mode 100644 util/PostgresMigrations/Migrations/20230118224536_RemoveDeviceUnknownVerification.cs create mode 100644 util/SqliteMigrations/Migrations/20230118212950_RemoveDeviceUnknownVerification.Designer.cs create mode 100644 util/SqliteMigrations/Migrations/20230118212950_RemoveDeviceUnknownVerification.cs diff --git a/src/Api/Controllers/TwoFactorController.cs b/src/Api/Controllers/TwoFactorController.cs index 4ba48c9d14..07d7197d66 100644 --- a/src/Api/Controllers/TwoFactorController.cs +++ b/src/Api/Controllers/TwoFactorController.cs @@ -295,21 +295,14 @@ public class TwoFactorController : Controller if (await _verifyAuthRequestCommand .VerifyAuthRequestAsync(new Guid(model.AuthRequestId), model.AuthRequestAccessCode)) { - var isBecauseNewDeviceLogin = await IsNewDeviceLoginAsync(user, model); - - await _userService.SendTwoFactorEmailAsync(user, isBecauseNewDeviceLogin); + await _userService.SendTwoFactorEmailAsync(user); return; } } - else + else if (await _userService.VerifySecretAsync(user, model.Secret)) { - if (await _userService.VerifySecretAsync(user, model.Secret)) - { - var isBecauseNewDeviceLogin = await IsNewDeviceLoginAsync(user, model); - - await _userService.SendTwoFactorEmailAsync(user, isBecauseNewDeviceLogin); - return; - } + await _userService.SendTwoFactorEmailAsync(user); + return; } } @@ -390,41 +383,18 @@ public class TwoFactorController : Controller } } + [Obsolete("Leaving this for backwards compatibilty on clients")] [HttpGet("get-device-verification-settings")] - public async Task GetDeviceVerificationSettings() + public Task GetDeviceVerificationSettings() { - var user = await _userService.GetUserByPrincipalAsync(User); - if (user == null) - { - throw new UnauthorizedAccessException(); - } - - if (User.Claims.HasSsoIdP()) - { - return new DeviceVerificationResponseModel(false, false); - } - - var canUserEditDeviceVerificationSettings = _userService.CanEditDeviceVerificationSettings(user); - return new DeviceVerificationResponseModel(canUserEditDeviceVerificationSettings, canUserEditDeviceVerificationSettings && user.UnknownDeviceVerificationEnabled); + return Task.FromResult(new DeviceVerificationResponseModel(false, false)); } + [Obsolete("Leaving this for backwards compatibilty on clients")] [HttpPut("device-verification-settings")] - public async Task PutDeviceVerificationSettings([FromBody] DeviceVerificationRequestModel model) + public Task PutDeviceVerificationSettings([FromBody] DeviceVerificationRequestModel model) { - var user = await _userService.GetUserByPrincipalAsync(User); - if (user == null) - { - throw new UnauthorizedAccessException(); - } - if (!_userService.CanEditDeviceVerificationSettings(user) - || User.Claims.HasSsoIdP()) - { - throw new InvalidOperationException("Can't update device verification settings"); - } - - model.ToUser(user); - await _userService.SaveUserAsync(user); - return new DeviceVerificationResponseModel(true, user.UnknownDeviceVerificationEnabled); + return Task.FromResult(new DeviceVerificationResponseModel(false, false)); } private async Task CheckAsync(SecretVerificationRequestModel model, bool premium) @@ -467,17 +437,4 @@ public class TwoFactorController : Controller await Task.Delay(500); } } - - private async Task IsNewDeviceLoginAsync(User user, TwoFactorEmailRequestModel model) - { - if (user.GetTwoFactorProvider(TwoFactorProviderType.Email) is null - && - await _userService.Needs2FABecauseNewDeviceAsync(user, model.DeviceIdentifier, null)) - { - model.ToUser(user); - return true; - } - - return false; - } } diff --git a/src/Api/Models/Request/DeviceVerificationRequestModel.cs b/src/Api/Models/Request/DeviceVerificationRequestModel.cs index d81471916b..466010af1f 100644 --- a/src/Api/Models/Request/DeviceVerificationRequestModel.cs +++ b/src/Api/Models/Request/DeviceVerificationRequestModel.cs @@ -1,16 +1,10 @@ using System.ComponentModel.DataAnnotations; -using Bit.Core.Entities; namespace Bit.Api.Models.Request; public class DeviceVerificationRequestModel { + [Obsolete("Leaving this for backwards compatibilty on clients")] [Required] public bool UnknownDeviceVerificationEnabled { get; set; } - - public User ToUser(User user) - { - user.UnknownDeviceVerificationEnabled = UnknownDeviceVerificationEnabled; - return user; - } } diff --git a/src/Api/Models/Request/TwoFactorRequestModels.cs b/src/Api/Models/Request/TwoFactorRequestModels.cs index 6f34432d78..18ba944ea8 100644 --- a/src/Api/Models/Request/TwoFactorRequestModels.cs +++ b/src/Api/Models/Request/TwoFactorRequestModels.cs @@ -202,8 +202,6 @@ public class TwoFactorEmailRequestModel : SecretVerificationRequestModel [StringLength(256)] public string Email { get; set; } - public string DeviceIdentifier { get; set; } - public string AuthRequestId { get; set; } public User ToUser(User extistingUser) diff --git a/src/Api/Models/Response/DeviceVerificationResponseModel.cs b/src/Api/Models/Response/DeviceVerificationResponseModel.cs index 0358ff7771..d38e65ee80 100644 --- a/src/Api/Models/Response/DeviceVerificationResponseModel.cs +++ b/src/Api/Models/Response/DeviceVerificationResponseModel.cs @@ -2,6 +2,7 @@ namespace Bit.Api.Models.Response; +[Obsolete("Leaving this for backwards compatibilty on clients")] public class DeviceVerificationResponseModel : ResponseModel { public DeviceVerificationResponseModel(bool isDeviceVerificationSectionEnabled, bool unknownDeviceVerificationEnabled) diff --git a/src/Core/Entities/User.cs b/src/Core/Entities/User.cs index 3b59729c14..d54bf0a308 100644 --- a/src/Core/Entities/User.cs +++ b/src/Core/Entities/User.cs @@ -62,7 +62,6 @@ public class User : ITableObject, ISubscriber, IStorable, IStorableSubscri public bool UsesKeyConnector { get; set; } public int FailedLoginCount { get; set; } public DateTime? LastFailedLoginDate { get; set; } - public bool UnknownDeviceVerificationEnabled { get; set; } [MaxLength(7)] public string AvatarColor { get; set; } public DateTime? LastPasswordChangeDate { get; set; } diff --git a/src/Core/MailTemplates/Handlebars/NewDeviceLoginTwoFactorEmail.html.hbs b/src/Core/MailTemplates/Handlebars/NewDeviceLoginTwoFactorEmail.html.hbs deleted file mode 100644 index d44380b577..0000000000 --- a/src/Core/MailTemplates/Handlebars/NewDeviceLoginTwoFactorEmail.html.hbs +++ /dev/null @@ -1,19 +0,0 @@ -{{#>FullHtmlLayout}} - - - - - - - - - - -
- Your two-step verification code is: {{Token}} -
- Use this code to complete logging in with Bitwarden. -
- This email was sent because you are logging in from a device we don’t recognize. If you did not request this code, you may want to change your master password. You can view our tips for selecting a secure master password here. -
-{{/FullHtmlLayout}} diff --git a/src/Core/MailTemplates/Handlebars/NewDeviceLoginTwoFactorEmail.text.hbs b/src/Core/MailTemplates/Handlebars/NewDeviceLoginTwoFactorEmail.text.hbs deleted file mode 100644 index 94a0f4a09a..0000000000 --- a/src/Core/MailTemplates/Handlebars/NewDeviceLoginTwoFactorEmail.text.hbs +++ /dev/null @@ -1,7 +0,0 @@ -{{#>BasicTextLayout}} -Your two-step verification code is: {{Token}} - -Use this code to complete logging in with Bitwarden. - -This email was sent because you are logging in from a device we don’t recognize. If you did not request this code, you may want to change your master password (https://bitwarden.com/help/master-password/#change-master-password). You can view our tips for selecting a secure master password here (https://bitwarden.com/blog/picking-the-right-password-for-your-password-manager/). -{{/BasicTextLayout}} \ No newline at end of file diff --git a/src/Core/Services/IMailService.cs b/src/Core/Services/IMailService.cs index a53ac63d22..9c86a445d5 100644 --- a/src/Core/Services/IMailService.cs +++ b/src/Core/Services/IMailService.cs @@ -13,7 +13,6 @@ public interface IMailService Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail); Task SendChangeEmailEmailAsync(string newEmailAddress, string token); Task SendTwoFactorEmailAsync(string email, string token); - Task SendNewDeviceLoginTwoFactorEmailAsync(string email, string token); Task SendNoMasterPasswordHintEmailAsync(string email); Task SendMasterPasswordHintEmailAsync(string email, string hint); Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token); diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index 620e9264f7..fd66feed35 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -19,7 +19,7 @@ public interface IUserService Task RegisterUserAsync(User user, string masterPassword, string token, Guid? orgUserId); Task RegisterUserAsync(User user); Task SendMasterPasswordHintAsync(string email); - Task SendTwoFactorEmailAsync(User user, bool isBecauseNewDeviceLogin = false); + Task SendTwoFactorEmailAsync(User user); Task VerifyTwoFactorEmailAsync(User user, string token); Task StartWebAuthnRegistrationAsync(User user); Task DeleteWebAuthnKeyAsync(User user, int id); @@ -76,6 +76,4 @@ public interface IUserService Task SendOTPAsync(User user); Task VerifyOTPAsync(User user, string token); Task VerifySecretAsync(User user, string secret); - Task Needs2FABecauseNewDeviceAsync(User user, string deviceIdentifier, string grantType); - bool CanEditDeviceVerificationSettings(User user); } diff --git a/src/Core/Services/Implementations/EmergencyAccessService.cs b/src/Core/Services/Implementations/EmergencyAccessService.cs index e48000b525..f5ddf7adfa 100644 --- a/src/Core/Services/Implementations/EmergencyAccessService.cs +++ b/src/Core/Services/Implementations/EmergencyAccessService.cs @@ -323,7 +323,6 @@ public class EmergencyAccessService : IEmergencyAccessService grantor.Key = key; // Disable TwoFactor providers since they will otherwise block logins grantor.SetTwoFactorProviders(new Dictionary()); - grantor.UnknownDeviceVerificationEnabled = false; await _userRepository.ReplaceAsync(grantor); // Remove grantor from all organizations unless Owner diff --git a/src/Core/Services/Implementations/HandlebarsMailService.cs b/src/Core/Services/Implementations/HandlebarsMailService.cs index f4190593dc..89edb7f097 100644 --- a/src/Core/Services/Implementations/HandlebarsMailService.cs +++ b/src/Core/Services/Implementations/HandlebarsMailService.cs @@ -113,21 +113,6 @@ public class HandlebarsMailService : IMailService await _mailDeliveryService.SendEmailAsync(message); } - public async Task SendNewDeviceLoginTwoFactorEmailAsync(string email, string token) - { - var message = CreateDefaultMessage("New Device Login Verification Code", email); - var model = new EmailTokenViewModel - { - Token = token, - WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash, - SiteName = _globalSettings.SiteName - }; - await AddMessageContentAsync(message, "NewDeviceLoginTwoFactorEmail", model); - message.MetaData.Add("SendGridBypassListManagement", true); - message.Category = "TwoFactorEmail"; - await _mailDeliveryService.SendEmailAsync(message); - } - public async Task SendMasterPasswordHintEmailAsync(string email, string hint) { var message = CreateDefaultMessage("Your Master Password Hint", email); diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 3045797490..659ace42b7 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -46,7 +46,6 @@ public class UserService : UserManager, IUserService, IDisposable private readonly IGlobalSettings _globalSettings; private readonly IOrganizationService _organizationService; private readonly IProviderUserRepository _providerUserRepository; - private readonly IDeviceRepository _deviceRepository; private readonly IStripeSyncService _stripeSyncService; public UserService( @@ -77,7 +76,6 @@ public class UserService : UserManager, IUserService, IDisposable IGlobalSettings globalSettings, IOrganizationService organizationService, IProviderUserRepository providerUserRepository, - IDeviceRepository deviceRepository, IStripeSyncService stripeSyncService) : base( store, @@ -113,7 +111,6 @@ public class UserService : UserManager, IUserService, IDisposable _globalSettings = globalSettings; _organizationService = organizationService; _providerUserRepository = providerUserRepository; - _deviceRepository = deviceRepository; _stripeSyncService = stripeSyncService; } @@ -353,7 +350,7 @@ public class UserService : UserManager, IUserService, IDisposable await _mailService.SendMasterPasswordHintEmailAsync(email, user.MasterPasswordHint); } - public async Task SendTwoFactorEmailAsync(User user, bool isBecauseNewDeviceLogin = false) + public async Task SendTwoFactorEmailAsync(User user) { var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email); if (provider == null || provider.MetaData == null || !provider.MetaData.ContainsKey("Email")) @@ -365,14 +362,7 @@ public class UserService : UserManager, IUserService, IDisposable var token = await base.GenerateUserTokenAsync(user, TokenOptions.DefaultEmailProvider, "2faEmail:" + email); - if (isBecauseNewDeviceLogin) - { - await _mailService.SendNewDeviceLoginTwoFactorEmailAsync(email, token); - } - else - { - await _mailService.SendTwoFactorEmailAsync(email, token); - } + await _mailService.SendTwoFactorEmailAsync(email, token); } public async Task VerifyTwoFactorEmailAsync(User user, string token) @@ -1478,36 +1468,4 @@ public class UserService : UserManager, IUserService, IDisposable ? await VerifyOTPAsync(user, secret) : await CheckPasswordAsync(user, secret); } - - public async Task Needs2FABecauseNewDeviceAsync(User user, string deviceIdentifier, string grantType) - { - return CanEditDeviceVerificationSettings(user) - && user.UnknownDeviceVerificationEnabled - && grantType != "authorization_code" - && await IsNewDeviceAndNotTheFirstOneAsync(user, deviceIdentifier); - } - - public bool CanEditDeviceVerificationSettings(User user) - { - return _globalSettings.TwoFactorAuth.EmailOnNewDeviceLogin - && user.EmailVerified - && !user.UsesKeyConnector - && !(user.GetTwoFactorProviders()?.Any() ?? false); - } - - private async Task IsNewDeviceAndNotTheFirstOneAsync(User user, string deviceIdentifier) - { - if (user == null) - { - return default; - } - - var devices = await _deviceRepository.GetManyByUserIdAsync(user.Id); - if (!devices.Any()) - { - return false; - } - - return !devices.Any(d => d.Identifier == deviceIdentifier); - } } diff --git a/src/Core/Services/NoopImplementations/NoopMailService.cs b/src/Core/Services/NoopImplementations/NoopMailService.cs index 36fb175ab4..9d7b4698ae 100644 --- a/src/Core/Services/NoopImplementations/NoopMailService.cs +++ b/src/Core/Services/NoopImplementations/NoopMailService.cs @@ -72,11 +72,6 @@ public class NoopMailService : IMailService return Task.FromResult(0); } - public Task SendNewDeviceLoginTwoFactorEmailAsync(string email, string token) - { - return Task.CompletedTask; - } - public Task SendWelcomeEmailAsync(User user) { return Task.FromResult(0); diff --git a/src/Core/Settings/GlobalSettings.cs b/src/Core/Settings/GlobalSettings.cs index 6a1198fc1e..44820c3040 100644 --- a/src/Core/Settings/GlobalSettings.cs +++ b/src/Core/Settings/GlobalSettings.cs @@ -73,7 +73,6 @@ public class GlobalSettings : IGlobalSettings public virtual AppleIapSettings AppleIap { get; set; } = new AppleIapSettings(); public virtual ISsoSettings Sso { get; set; } = new SsoSettings(); public virtual StripeSettings Stripe { get; set; } = new StripeSettings(); - public virtual ITwoFactorAuthSettings TwoFactorAuth { get; set; } = new TwoFactorAuthSettings(); public virtual DistributedIpRateLimitingSettings DistributedIpRateLimiting { get; set; } = new DistributedIpRateLimitingSettings(); public virtual IPasswordlessAuthSettings PasswordlessAuth { get; set; } = new PasswordlessAuthSettings(); @@ -511,11 +510,6 @@ public class GlobalSettings : IGlobalSettings public int MaxNetworkRetries { get; set; } = 2; } - public class TwoFactorAuthSettings : ITwoFactorAuthSettings - { - public bool EmailOnNewDeviceLogin { get; set; } = false; - } - public class DistributedIpRateLimitingSettings { public bool Enabled { get; set; } = true; diff --git a/src/Core/Settings/IGlobalSettings.cs b/src/Core/Settings/IGlobalSettings.cs index 724c595299..49d016253d 100644 --- a/src/Core/Settings/IGlobalSettings.cs +++ b/src/Core/Settings/IGlobalSettings.cs @@ -13,7 +13,6 @@ public interface IGlobalSettings IFileStorageSettings Attachment { get; set; } IConnectionStringSettings Storage { get; set; } IBaseServiceUriSettings BaseServiceUri { get; set; } - ITwoFactorAuthSettings TwoFactorAuth { get; set; } ISsoSettings Sso { get; set; } ILogLevelSettings MinLogLevel { get; set; } IPasswordlessAuthSettings PasswordlessAuth { get; set; } diff --git a/src/Core/Settings/ITwoFactorAuthSettings.cs b/src/Core/Settings/ITwoFactorAuthSettings.cs deleted file mode 100644 index 2e11e65079..0000000000 --- a/src/Core/Settings/ITwoFactorAuthSettings.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bit.Core.Settings; - -public interface ITwoFactorAuthSettings -{ - bool EmailOnNewDeviceLogin { get; set; } -} diff --git a/src/Identity/IdentityServer/BaseRequestValidator.cs b/src/Identity/IdentityServer/BaseRequestValidator.cs index d8830e0867..ccec87fdb6 100644 --- a/src/Identity/IdentityServer/BaseRequestValidator.cs +++ b/src/Identity/IdentityServer/BaseRequestValidator.cs @@ -100,24 +100,20 @@ public abstract class BaseRequestValidator where T : class return; } - var (isTwoFactorRequired, requires2FABecauseNewDevice, twoFactorOrganization) = await RequiresTwoFactorAsync(user, request); + var (isTwoFactorRequired, twoFactorOrganization) = await RequiresTwoFactorAsync(user, request); if (isTwoFactorRequired) { // Just defaulting it var twoFactorProviderType = TwoFactorProviderType.Authenticator; if (!twoFactorRequest || !Enum.TryParse(twoFactorProvider, out twoFactorProviderType)) { - await BuildTwoFactorResultAsync(user, twoFactorOrganization, context, requires2FABecauseNewDevice); + await BuildTwoFactorResultAsync(user, twoFactorOrganization, context); return; } - BeforeVerifyTwoFactor(user, twoFactorProviderType, requires2FABecauseNewDevice); - var verified = await VerifyTwoFactor(user, twoFactorOrganization, twoFactorProviderType, twoFactorToken); - AfterVerifyTwoFactor(user, twoFactorProviderType, requires2FABecauseNewDevice); - if ((!verified || isBot) && twoFactorProviderType != TwoFactorProviderType.Remember) { await UpdateFailedAuthDetailsAsync(user, true, !validatorContext.KnownDevice); @@ -128,7 +124,7 @@ public abstract class BaseRequestValidator where T : class { // Delay for brute force. await Task.Delay(2000); - await BuildTwoFactorResultAsync(user, twoFactorOrganization, context, requires2FABecauseNewDevice); + await BuildTwoFactorResultAsync(user, twoFactorOrganization, context); return; } } @@ -201,7 +197,7 @@ public abstract class BaseRequestValidator where T : class await SetSuccessResult(context, user, claims, customResponse); } - protected async Task BuildTwoFactorResultAsync(User user, Organization organization, T context, bool requires2FABecauseNewDevice) + protected async Task BuildTwoFactorResultAsync(User user, Organization organization, T context) { var providerKeys = new List(); var providers = new Dictionary>(); @@ -226,23 +222,8 @@ public abstract class BaseRequestValidator where T : class if (!enabledProviders.Any()) { - if (!requires2FABecauseNewDevice) - { - await BuildErrorResultAsync("No two-step providers enabled.", false, context, user); - return; - } - - var emailProvider = new TwoFactorProvider - { - MetaData = new Dictionary { ["Email"] = user.Email.ToLowerInvariant() }, - Enabled = true - }; - enabledProviders.Add(new KeyValuePair( - TwoFactorProviderType.Email, emailProvider)); - user.SetTwoFactorProviders(new Dictionary - { - [TwoFactorProviderType.Email] = emailProvider - }); + await BuildErrorResultAsync("No two-step providers enabled.", false, context, user); + return; } foreach (var provider in enabledProviders) @@ -262,7 +243,7 @@ public abstract class BaseRequestValidator where T : class if (enabledProviders.Count() == 1 && enabledProviders.First().Key == TwoFactorProviderType.Email) { // Send email now if this is their only 2FA method - await _userService.SendTwoFactorEmailAsync(user, requires2FABecauseNewDevice); + await _userService.SendTwoFactorEmailAsync(user); } } @@ -298,12 +279,12 @@ public abstract class BaseRequestValidator where T : class protected abstract void SetErrorResult(T context, Dictionary customResponse); - private async Task> RequiresTwoFactorAsync(User user, ValidatedTokenRequest request) + private async Task> RequiresTwoFactorAsync(User user, ValidatedTokenRequest request) { if (request.GrantType == "client_credentials") { // Do not require MFA for api key logins - return new Tuple(false, false, null); + return new Tuple(false, null); } var individualRequired = _userManager.SupportsUserTwoFactor && @@ -325,17 +306,7 @@ public abstract class BaseRequestValidator where T : class } } - var requires2FA = individualRequired || firstEnabledOrg != null; - var requires2FABecauseNewDevice = !requires2FA - && - await _userService.Needs2FABecauseNewDeviceAsync( - user, - GetDeviceFromRequest(request)?.Identifier, - request.GrantType); - - requires2FA = requires2FA || requires2FABecauseNewDevice; - - return new Tuple(requires2FA, requires2FABecauseNewDevice, firstEnabledOrg); + return new Tuple(individualRequired || firstEnabledOrg != null, firstEnabledOrg); } private async Task IsValidAuthTypeAsync(User user, string grantType) @@ -413,29 +384,6 @@ public abstract class BaseRequestValidator where T : class }; } - private void BeforeVerifyTwoFactor(User user, TwoFactorProviderType type, bool requires2FABecauseNewDevice) - { - if (type == TwoFactorProviderType.Email && requires2FABecauseNewDevice) - { - user.SetTwoFactorProviders(new Dictionary - { - [TwoFactorProviderType.Email] = new TwoFactorProvider - { - MetaData = new Dictionary { ["Email"] = user.Email.ToLowerInvariant() }, - Enabled = true - } - }); - } - } - - private void AfterVerifyTwoFactor(User user, TwoFactorProviderType type, bool requires2FABecauseNewDevice) - { - if (type == TwoFactorProviderType.Email && requires2FABecauseNewDevice) - { - user.ClearTwoFactorProviders(); - } - } - private async Task VerifyTwoFactor(User user, Organization organization, TwoFactorProviderType type, string token) { diff --git a/src/Sql/dbo_future/Stored Procedures/User_Create.sql b/src/Sql/dbo_future/Stored Procedures/User_Create.sql new file mode 100644 index 0000000000..7139153950 --- /dev/null +++ b/src/Sql/dbo_future/Stored Procedures/User_Create.sql @@ -0,0 +1,120 @@ +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), + @ForcePasswordReset BIT = 0, + @UsesKeyConnector BIT = 0, + @FailedLoginCount INT = 0, + @LastFailedLoginDate DATETIME2(7), + @AvatarColor VARCHAR(7) = NULL +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], + [ForcePasswordReset], + [UsesKeyConnector], + [FailedLoginCount], + [LastFailedLoginDate], + [AvatarColor] + ) + 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, + @ForcePasswordReset, + @UsesKeyConnector, + @FailedLoginCount, + @LastFailedLoginDate, + @AvatarColor + ) +END diff --git a/src/Sql/dbo_future/Stored Procedures/User_Update.sql b/src/Sql/dbo_future/Stored Procedures/User_Update.sql new file mode 100644 index 0000000000..1ef314af8e --- /dev/null +++ b/src/Sql/dbo_future/Stored Procedures/User_Update.sql @@ -0,0 +1,82 @@ +CREATE PROCEDURE [dbo].[User_Update] + @Id UNIQUEIDENTIFIER, + @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), + @ForcePasswordReset BIT = 0, + @UsesKeyConnector BIT = 0, + @FailedLoginCount INT, + @LastFailedLoginDate DATETIME2(7), + @AvatarColor VARCHAR(7) +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[User] + SET + [Name] = @Name, + [Email] = @Email, + [EmailVerified] = @EmailVerified, + [MasterPassword] = @MasterPassword, + [MasterPasswordHint] = @MasterPasswordHint, + [Culture] = @Culture, + [SecurityStamp] = @SecurityStamp, + [TwoFactorProviders] = @TwoFactorProviders, + [TwoFactorRecoveryCode] = @TwoFactorRecoveryCode, + [EquivalentDomains] = @EquivalentDomains, + [ExcludedGlobalEquivalentDomains] = @ExcludedGlobalEquivalentDomains, + [AccountRevisionDate] = @AccountRevisionDate, + [Key] = @Key, + [PublicKey] = @PublicKey, + [PrivateKey] = @PrivateKey, + [Premium] = @Premium, + [PremiumExpirationDate] = @PremiumExpirationDate, + [RenewalReminderDate] = @RenewalReminderDate, + [Storage] = @Storage, + [MaxStorageGb] = @MaxStorageGb, + [Gateway] = @Gateway, + [GatewayCustomerId] = @GatewayCustomerId, + [GatewaySubscriptionId] = @GatewaySubscriptionId, + [ReferenceData] = @ReferenceData, + [LicenseKey] = @LicenseKey, + [Kdf] = @Kdf, + [KdfIterations] = @KdfIterations, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate, + [ApiKey] = @ApiKey, + [ForcePasswordReset] = @ForcePasswordReset, + [UsesKeyConnector] = @UsesKeyConnector, + [FailedLoginCount] = @FailedLoginCount, + [LastFailedLoginDate] = @LastFailedLoginDate, + [AvatarColor] = @AvatarColor + WHERE + [Id] = @Id +END diff --git a/src/Sql/dbo_future/Tables/User.sql b/src/Sql/dbo_future/Tables/User.sql new file mode 100644 index 0000000000..e2d681d1e2 --- /dev/null +++ b/src/Sql/dbo_future/Tables/User.sql @@ -0,0 +1,49 @@ +CREATE TABLE [dbo].[User] ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [Name] NVARCHAR (50) NULL, + [Email] NVARCHAR (256) NOT NULL, + [EmailVerified] BIT NOT NULL, + [MasterPassword] NVARCHAR (300) NULL, + [MasterPasswordHint] NVARCHAR (50) NULL, + [Culture] NVARCHAR (10) NOT NULL, + [SecurityStamp] NVARCHAR (50) NOT NULL, + [TwoFactorProviders] NVARCHAR (MAX) NULL, + [TwoFactorRecoveryCode] NVARCHAR (32) NULL, + [EquivalentDomains] NVARCHAR (MAX) NULL, + [ExcludedGlobalEquivalentDomains] NVARCHAR (MAX) NULL, + [AccountRevisionDate] DATETIME2 (7) NOT NULL, + [Key] VARCHAR (MAX) NULL, + [PublicKey] VARCHAR (MAX) NULL, + [PrivateKey] VARCHAR (MAX) NULL, + [Premium] BIT NOT NULL, + [PremiumExpirationDate] DATETIME2 (7) NULL, + [RenewalReminderDate] DATETIME2 (7) NULL, + [Storage] BIGINT NULL, + [MaxStorageGb] SMALLINT NULL, + [Gateway] TINYINT NULL, + [GatewayCustomerId] VARCHAR (50) NULL, + [GatewaySubscriptionId] VARCHAR (50) NULL, + [ReferenceData] NVARCHAR (MAX) NULL, + [LicenseKey] VARCHAR (100) NULL, + [Kdf] TINYINT NOT NULL, + [KdfIterations] INT NOT NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + [RevisionDate] DATETIME2 (7) NOT NULL, + [ApiKey] VARCHAR (30) NOT NULL, + [ForcePasswordReset] BIT NOT NULL, + [UsesKeyConnector] BIT NOT NULL, + [FailedLoginCount] INT CONSTRAINT [D_User_FailedLoginCount] DEFAULT ((0)) NOT NULL, + [LastFailedLoginDate] DATETIME2 (7) NULL, + [AvatarColor] VARCHAR(7) NULL, + CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ([Id] ASC) +); + + +GO +CREATE UNIQUE NONCLUSTERED INDEX [IX_User_Email] + ON [dbo].[User]([Email] ASC); + +GO +CREATE NONCLUSTERED INDEX [IX_User_Premium_PremiumExpirationDate_RenewalReminderDate] + ON [dbo].[User]([Premium] ASC, [PremiumExpirationDate] ASC, [RenewalReminderDate] ASC); + diff --git a/test/Core.Test/Services/EmergencyAccessServiceTests.cs b/test/Core.Test/Services/EmergencyAccessServiceTests.cs index 110aa4a25c..60cacf79fa 100644 --- a/test/Core.Test/Services/EmergencyAccessServiceTests.cs +++ b/test/Core.Test/Services/EmergencyAccessServiceTests.cs @@ -138,11 +138,10 @@ public class EmergencyAccessServiceTests } [Theory, BitAutoData] - public async Task PasswordAsync_Disables_2FA_Providers_And_Unknown_Device_Verification_On_The_Grantor( + public async Task PasswordAsync_Disables_2FA_Providers_On_The_Grantor( SutProvider sutProvider, User requestingUser, User grantor) { grantor.UsesKeyConnector = true; - grantor.UnknownDeviceVerificationEnabled = true; grantor.SetTwoFactorProviders(new Dictionary { [TwoFactorProviderType.Email] = new TwoFactorProvider @@ -164,7 +163,6 @@ public class EmergencyAccessServiceTests await sutProvider.Sut.PasswordAsync(Guid.NewGuid(), requestingUser, "blablahash", "blablakey"); - Assert.False(grantor.UnknownDeviceVerificationEnabled); Assert.Empty(grantor.GetTwoFactorProviders()); await sutProvider.GetDependency().Received().ReplaceAsync(grantor); } diff --git a/test/Core.Test/Services/UserServiceTests.cs b/test/Core.Test/Services/UserServiceTests.cs index 52d40e5781..3257f1feea 100644 --- a/test/Core.Test/Services/UserServiceTests.cs +++ b/test/Core.Test/Services/UserServiceTests.cs @@ -95,37 +95,6 @@ public class UserServiceTests .SendTwoFactorEmailAsync(email, token); } - [Theory, BitAutoData] - public async Task SendTwoFactorEmailBecauseNewDeviceLoginAsync_Success(SutProvider sutProvider, User user) - { - var email = user.Email.ToLowerInvariant(); - var token = "thisisatokentocompare"; - - var userTwoFactorTokenProvider = Substitute.For>(); - userTwoFactorTokenProvider - .CanGenerateTwoFactorTokenAsync(Arg.Any>(), user) - .Returns(Task.FromResult(true)); - userTwoFactorTokenProvider - .GenerateAsync("2faEmail:" + email, Arg.Any>(), user) - .Returns(Task.FromResult(token)); - - sutProvider.Sut.RegisterTokenProvider("Email", userTwoFactorTokenProvider); - - user.SetTwoFactorProviders(new Dictionary - { - [TwoFactorProviderType.Email] = new TwoFactorProvider - { - MetaData = new Dictionary { ["Email"] = email }, - Enabled = true - } - }); - await sutProvider.Sut.SendTwoFactorEmailAsync(user, true); - - await sutProvider.GetDependency() - .Received(1) - .SendNewDeviceLoginTwoFactorEmailAsync(email, token); - } - [Theory, BitAutoData] public async Task SendTwoFactorEmailAsync_ExceptionBecauseNoProviderOnUser(SutProvider sutProvider, User user) { @@ -164,199 +133,6 @@ public class UserServiceTests await Assert.ThrowsAsync("No email.", () => sutProvider.Sut.SendTwoFactorEmailAsync(user)); } - [Theory, BitAutoData] - public async Task Needs2FABecauseNewDeviceAsync_ReturnsTrue(SutProvider sutProvider, User user) - { - user.EmailVerified = true; - user.TwoFactorProviders = null; - user.UnknownDeviceVerificationEnabled = true; - const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc"; - const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a"; - - sutProvider.GetDependency() - .GetManyByUserIdAsync(user.Id) - .Returns(Task.FromResult>(new List - { - new Device { Identifier = deviceIdInRepo } - })); - - - sutProvider.GetDependency().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true); - - Assert.True(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "password")); - } - - [Theory, BitAutoData] - public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_GranType_Is_AuthorizationCode(SutProvider sutProvider, User user) - { - user.EmailVerified = true; - user.TwoFactorProviders = null; - const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc"; - const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a"; - - sutProvider.GetDependency() - .GetManyByUserIdAsync(user.Id) - .Returns(Task.FromResult>(new List - { - new Device { Identifier = deviceIdInRepo } - })); - - Assert.False(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "authorization_code")); - } - - [Theory, BitAutoData] - public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_Email_Is_Not_Verified(SutProvider sutProvider, User user) - { - user.EmailVerified = false; - user.TwoFactorProviders = null; - const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc"; - const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a"; - - sutProvider.GetDependency() - .GetManyByUserIdAsync(user.Id) - .Returns(Task.FromResult>(new List - { - new Device { Identifier = deviceIdInRepo } - })); - - Assert.False(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "password")); - } - - [Theory, BitAutoData] - public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_Is_The_First_Device(SutProvider sutProvider, User user) - { - user.EmailVerified = true; - user.TwoFactorProviders = null; - const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc"; - - sutProvider.GetDependency() - .GetManyByUserIdAsync(user.Id) - .Returns(Task.FromResult>(new List())); - - Assert.False(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "password")); - } - - [Theory, BitAutoData] - public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_DeviceId_Is_Already_In_Repo(SutProvider sutProvider, User user) - { - user.EmailVerified = true; - user.TwoFactorProviders = null; - const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc"; - - sutProvider.GetDependency() - .GetManyByUserIdAsync(user.Id) - .Returns(Task.FromResult>(new List - { - new Device { Identifier = deviceIdToCheck } - })); - - Assert.False(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "password")); - } - - [Theory, BitAutoData] - public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_GlobalSettings_2FA_EmailOnNewDeviceLogin_Is_Disabled(SutProvider sutProvider, User user) - { - user.EmailVerified = true; - user.TwoFactorProviders = null; - const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc"; - const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a"; - - sutProvider.GetDependency() - .GetManyByUserIdAsync(user.Id) - .Returns(Task.FromResult>(new List - { - new Device { Identifier = deviceIdInRepo } - })); - - sutProvider.GetDependency().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(false); - - Assert.False(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "password")); - } - - [Theory, BitAutoData] - public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_UnknownDeviceVerification_Is_Disabled(SutProvider sutProvider, User user) - { - user.EmailVerified = true; - user.TwoFactorProviders = null; - user.UnknownDeviceVerificationEnabled = false; - const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc"; - const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a"; - - sutProvider.GetDependency() - .GetManyByUserIdAsync(user.Id) - .Returns(Task.FromResult>(new List - { - new Device { Identifier = deviceIdInRepo } - })); - - sutProvider.GetDependency().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true); - - Assert.False(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "password")); - } - - [Theory, BitAutoData] - public void CanEditDeviceVerificationSettings_ReturnsTrue(SutProvider sutProvider, User user) - { - user.EmailVerified = true; - user.TwoFactorProviders = null; - - sutProvider.GetDependency().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true); - - Assert.True(sutProvider.Sut.CanEditDeviceVerificationSettings(user)); - } - - [Theory, BitAutoData] - public void CanEditDeviceVerificationSettings_ReturnsFalse_When_GlobalSettings_2FA_EmailOnNewDeviceLogin_Is_Disabled(SutProvider sutProvider, User user) - { - user.EmailVerified = true; - user.TwoFactorProviders = null; - - sutProvider.GetDependency().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(false); - - Assert.False(sutProvider.Sut.CanEditDeviceVerificationSettings(user)); - } - - [Theory, BitAutoData] - public void CanEditDeviceVerificationSettings_ReturnsFalse_When_Email_Is_Not_Verified(SutProvider sutProvider, User user) - { - user.EmailVerified = false; - user.TwoFactorProviders = null; - - sutProvider.GetDependency().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true); - - Assert.False(sutProvider.Sut.CanEditDeviceVerificationSettings(user)); - } - - [Theory, BitAutoData] - public void CanEditDeviceVerificationSettings_ReturnsFalse_When_User_Uses_Key_Connector(SutProvider sutProvider, User user) - { - user.EmailVerified = true; - user.TwoFactorProviders = null; - user.UsesKeyConnector = true; - - sutProvider.GetDependency().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true); - - Assert.False(sutProvider.Sut.CanEditDeviceVerificationSettings(user)); - } - - [Theory, BitAutoData] - public void CanEditDeviceVerificationSettings_ReturnsFalse_When_User_Has_A_2FA_Already_Set_Up(SutProvider sutProvider, User user) - { - user.EmailVerified = true; - user.SetTwoFactorProviders(new Dictionary - { - [TwoFactorProviderType.Email] = new TwoFactorProvider - { - MetaData = new Dictionary { ["Email"] = "asdfasf" }, - Enabled = true - } - }); - - sutProvider.GetDependency().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true); - - Assert.False(sutProvider.Sut.CanEditDeviceVerificationSettings(user)); - } - [Theory, BitAutoData] public async void HasPremiumFromOrganization_Returns_False_If_No_Orgs(SutProvider sutProvider, User user) { diff --git a/util/Migrator/DbScripts_future/2023-01-FutureMigration.sql b/util/Migrator/DbScripts_future/2023-01-FutureMigration.sql new file mode 100644 index 0000000000..9f725c7be2 --- /dev/null +++ b/util/Migrator/DbScripts_future/2023-01-FutureMigration.sql @@ -0,0 +1,230 @@ +-- Table: User (UnknownDeviceVerificationEnabled) +IF COL_LENGTH('[dbo].[User]', 'UnknownDeviceVerificationEnabled') IS NOT NULL +BEGIN + ALTER TABLE + [dbo].[User] + DROP CONSTRAINT + [D_User_UnknownDeviceVerificationEnabled] + ALTER TABLE + [dbo].[User] + DROP COLUMN + [UnknownDeviceVerificationEnabled] +END +GO + +-- View: User +CREATE OR ALTER VIEW [dbo].[UserView] +AS +SELECT + * +FROM + [dbo].[User] +GO + +-- Stored Procedure: User_Create +CREATE OR ALTER 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), + @ForcePasswordReset BIT = 0, + @UsesKeyConnector BIT = 0, + @FailedLoginCount INT = 0, + @LastFailedLoginDate DATETIME2(7), + @AvatarColor VARCHAR(7) = NULL +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], + [ForcePasswordReset], + [UsesKeyConnector], + [FailedLoginCount], + [LastFailedLoginDate], + [AvatarColor] + ) + 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, + @ForcePasswordReset, + @UsesKeyConnector, + @FailedLoginCount, + @LastFailedLoginDate, + @AvatarColor + ) +END +GO + +-- Stored Procedure: User_Update +CREATE OR ALTER PROCEDURE [dbo].[User_Update] + @Id UNIQUEIDENTIFIER, + @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), + @ForcePasswordReset BIT = 0, + @UsesKeyConnector BIT = 0, + @FailedLoginCount INT, + @LastFailedLoginDate DATETIME2(7), + @AvatarColor VARCHAR(7) +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[User] + SET + [Name] = @Name, + [Email] = @Email, + [EmailVerified] = @EmailVerified, + [MasterPassword] = @MasterPassword, + [MasterPasswordHint] = @MasterPasswordHint, + [Culture] = @Culture, + [SecurityStamp] = @SecurityStamp, + [TwoFactorProviders] = @TwoFactorProviders, + [TwoFactorRecoveryCode] = @TwoFactorRecoveryCode, + [EquivalentDomains] = @EquivalentDomains, + [ExcludedGlobalEquivalentDomains] = @ExcludedGlobalEquivalentDomains, + [AccountRevisionDate] = @AccountRevisionDate, + [Key] = @Key, + [PublicKey] = @PublicKey, + [PrivateKey] = @PrivateKey, + [Premium] = @Premium, + [PremiumExpirationDate] = @PremiumExpirationDate, + [RenewalReminderDate] = @RenewalReminderDate, + [Storage] = @Storage, + [MaxStorageGb] = @MaxStorageGb, + [Gateway] = @Gateway, + [GatewayCustomerId] = @GatewayCustomerId, + [GatewaySubscriptionId] = @GatewaySubscriptionId, + [ReferenceData] = @ReferenceData, + [LicenseKey] = @LicenseKey, + [Kdf] = @Kdf, + [KdfIterations] = @KdfIterations, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate, + [ApiKey] = @ApiKey, + [ForcePasswordReset] = @ForcePasswordReset, + [UsesKeyConnector] = @UsesKeyConnector, + [FailedLoginCount] = @FailedLoginCount, + [LastFailedLoginDate] = @LastFailedLoginDate, + [AvatarColor] = @AvatarColor + WHERE + [Id] = @Id +END +GO diff --git a/util/MySqlMigrations/Migrations/20230118225349_RemoveDeviceUnknownVerification.Designer.cs b/util/MySqlMigrations/Migrations/20230118225349_RemoveDeviceUnknownVerification.Designer.cs new file mode 100644 index 0000000000..1cdec6545e --- /dev/null +++ b/util/MySqlMigrations/Migrations/20230118225349_RemoveDeviceUnknownVerification.Designer.cs @@ -0,0 +1,2095 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20230118225349_RemoveDeviceUnknownVerification")] + partial class RemoveDeviceUnknownVerification + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.12") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Read") + .HasColumnType("tinyint(1)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Write") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator("Discriminator").HasValue("AccessPolicy"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ClientSecret") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPayload") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Scope") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestFingerprint") + .HasColumnType("longtext"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("SystemUser") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Project", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("char(36)"); + + b.Property("SecretsId") + .HasColumnType("char(36)"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId"); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany() + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany() + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Connections"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20230118225349_RemoveDeviceUnknownVerification.cs b/util/MySqlMigrations/Migrations/20230118225349_RemoveDeviceUnknownVerification.cs new file mode 100644 index 0000000000..06c4daaf3b --- /dev/null +++ b/util/MySqlMigrations/Migrations/20230118225349_RemoveDeviceUnknownVerification.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +public partial class RemoveDeviceUnknownVerification : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UnknownDeviceVerificationEnabled", + table: "User"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UnknownDeviceVerificationEnabled", + table: "User", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } +} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index 4c9aac6f5d..dd91c2bccc 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -1329,9 +1329,6 @@ namespace Bit.MySqlMigrations.Migrations .HasMaxLength(32) .HasColumnType("varchar(32)"); - b.Property("UnknownDeviceVerificationEnabled") - .HasColumnType("tinyint(1)"); - b.Property("UsesKeyConnector") .HasColumnType("tinyint(1)"); diff --git a/util/PostgresMigrations/Migrations/20230118224536_RemoveDeviceUnknownVerification.Designer.cs b/util/PostgresMigrations/Migrations/20230118224536_RemoveDeviceUnknownVerification.Designer.cs new file mode 100644 index 0000000000..6f59829cd5 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20230118224536_RemoveDeviceUnknownVerification.Designer.cs @@ -0,0 +1,2106 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20230118224536_RemoveDeviceUnknownVerification")] + partial class RemoveDeviceUnknownVerification + { + 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("ProductVersion", "6.0.12") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("text"); + + b.Property("Read") + .HasColumnType("boolean"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Write") + .HasColumnType("boolean"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator("Discriminator").HasValue("AccessPolicy"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientSecret") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPayload") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Scope") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestFingerprint") + .HasColumnType("text"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("SystemUser") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Project", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("uuid"); + + b.Property("SecretsId") + .HasColumnType("uuid"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId"); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany() + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany() + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Connections"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20230118224536_RemoveDeviceUnknownVerification.cs b/util/PostgresMigrations/Migrations/20230118224536_RemoveDeviceUnknownVerification.cs new file mode 100644 index 0000000000..89a4a24ade --- /dev/null +++ b/util/PostgresMigrations/Migrations/20230118224536_RemoveDeviceUnknownVerification.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +public partial class RemoveDeviceUnknownVerification : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UnknownDeviceVerificationEnabled", + table: "User"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UnknownDeviceVerificationEnabled", + table: "User", + type: "boolean", + nullable: false, + defaultValue: false); + } +} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index ae95db8f1b..48a862ae72 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -1340,9 +1340,6 @@ namespace Bit.PostgresMigrations.Migrations .HasMaxLength(32) .HasColumnType("character varying(32)"); - b.Property("UnknownDeviceVerificationEnabled") - .HasColumnType("boolean"); - b.Property("UsesKeyConnector") .HasColumnType("boolean"); diff --git a/util/SqliteMigrations/Migrations/20230118212950_RemoveDeviceUnknownVerification.Designer.cs b/util/SqliteMigrations/Migrations/20230118212950_RemoveDeviceUnknownVerification.Designer.cs new file mode 100644 index 0000000000..e1000e6a46 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20230118212950_RemoveDeviceUnknownVerification.Designer.cs @@ -0,0 +1,2093 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20230118212950_RemoveDeviceUnknownVerification")] + partial class RemoveDeviceUnknownVerification + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.12"); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Read") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Write") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator("Discriminator").HasValue("AccessPolicy"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientSecret") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPayload") + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpireAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Scope") + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property("RequestFingerprint") + .HasColumnType("TEXT"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ResponseDate") + .HasColumnType("TEXT"); + + b.Property("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Attachments") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Favorites") + .HasColumnType("TEXT"); + + b.Property("Folders") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Reprompt") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("GranteeId") + .HasColumnType("TEXT"); + + b.Property("GrantorId") + .HasColumnType("TEXT"); + + b.Property("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WaitTimeDays") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActingUserId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("PolicyId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("ProviderOrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SystemUser") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessAll") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxCollections") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("TEXT"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("SelfHost") + .HasColumnType("INTEGER"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("Use2fa") + .HasColumnType("INTEGER"); + + b.Property("UseApi") + .HasColumnType("INTEGER"); + + b.Property("UseCustomPermissions") + .HasColumnType("INTEGER"); + + b.Property("UseDirectory") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.Property("UseGroups") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property("UseScim") + .HasColumnType("INTEGER"); + + b.Property("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("UseSso") + .HasColumnType("INTEGER"); + + b.Property("UseTotp") + .HasColumnType("INTEGER"); + + b.Property("UsersGetPremium") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Config") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessAll") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Project", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Provider", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("TEXT"); + + b.Property("SecretsId") + .HasColumnType("TEXT"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId"); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany() + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany() + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Connections"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/SqliteMigrations/Migrations/20230118212950_RemoveDeviceUnknownVerification.cs b/util/SqliteMigrations/Migrations/20230118212950_RemoveDeviceUnknownVerification.cs new file mode 100644 index 0000000000..b6ae617a59 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20230118212950_RemoveDeviceUnknownVerification.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +public partial class RemoveDeviceUnknownVerification : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UnknownDeviceVerificationEnabled", + table: "User"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UnknownDeviceVerificationEnabled", + table: "User", + type: "INTEGER", + nullable: false, + defaultValue: false); + } +} diff --git a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs index b6564f5755..e990c66d13 100644 --- a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -1287,9 +1287,6 @@ namespace Bit.SqliteMigrations.Migrations .HasMaxLength(32) .HasColumnType("TEXT"); - b.Property("UnknownDeviceVerificationEnabled") - .HasColumnType("INTEGER"); - b.Property("UsesKeyConnector") .HasColumnType("INTEGER"); From 34544f229286be319ab6e7dba595f2c1e66e67ee Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Fri, 17 Feb 2023 13:19:21 -0500 Subject: [PATCH 35/49] [SG-1082]-Defect-Update stored procedure to properly determine is SSO is available (#2715) * Fixed SsoAvailble bug by using the enabled column from SsoConfig table, updated the existing query for EF Core * Added no tracking to ef query since it is read only --- .../OrganizationDomainSsoDetailsData.cs | 2 +- .../OrganizationDomainRepository.cs | 45 ++++++++----------- ...ganizationDomainSsoDetails_ReadByEmail.sql | 4 +- ...2-16_FixSsoAvailableOrganizationDomain.sql | 31 +++++++++++++ 4 files changed, 54 insertions(+), 28 deletions(-) create mode 100644 util/Migrator/DbScripts/2023-02-16_FixSsoAvailableOrganizationDomain.sql diff --git a/src/Core/Models/Data/Organizations/OrganizationDomainSsoDetailsData.cs b/src/Core/Models/Data/Organizations/OrganizationDomainSsoDetailsData.cs index 66b6081f1d..1a87db792e 100644 --- a/src/Core/Models/Data/Organizations/OrganizationDomainSsoDetailsData.cs +++ b/src/Core/Models/Data/Organizations/OrganizationDomainSsoDetailsData.cs @@ -10,7 +10,7 @@ public class OrganizationDomainSsoDetailsData public bool SsoAvailable { get; set; } public string OrganizationIdentifier { get; set; } public bool SsoRequired { get; set; } - public PolicyType PolicyType { get; set; } + public PolicyType? PolicyType { get; set; } public DateTime? VerifiedDate { get; set; } public bool OrganizationEnabled { get; set; } } diff --git a/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs b/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs index daafd7c958..1f3cd375e5 100644 --- a/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs @@ -74,32 +74,25 @@ public class OrganizationDomainRepository : Repository o.Id, od => od.OrganizationId, - (organization, domain) => new { resOrganization = organization, resDomain = domain }) - .Join(dbContext.Policies, o => o.resOrganization.Id, p => p.OrganizationId, - (combinedOrgDomain, policy) - => new - { - Organization = combinedOrgDomain.resOrganization, - Domain = combinedOrgDomain.resDomain, - Policy = policy - }) - .Select(x => new OrganizationDomainSsoDetailsData - { - OrganizationId = x.Organization.Id, - OrganizationName = x.Organization.Name, - SsoAvailable = x.Organization.UseSso, - OrganizationIdentifier = x.Organization.Identifier, - SsoRequired = x.Policy.Enabled, - VerifiedDate = x.Domain.VerifiedDate, - PolicyType = x.Policy.Type, - DomainName = x.Domain.DomainName, - OrganizationEnabled = x.Organization.Enabled - }) - .Where(y => y.DomainName == domainName - && y.OrganizationEnabled == true - && y.PolicyType.Equals(PolicyType.RequireSso)) + var ssoDetails = await (from o in dbContext.Organizations + from od in o.Domains + join s in dbContext.SsoConfigs on o.Id equals s.OrganizationId into sJoin + from s in sJoin.DefaultIfEmpty() + join p in dbContext.Policies.Where(p => p.Type == PolicyType.RequireSso) on o.Id + equals p.OrganizationId into pJoin + from p in pJoin.DefaultIfEmpty() + where od.DomainName == domainName && o.Enabled + select new OrganizationDomainSsoDetailsData + { + OrganizationId = o.Id, + OrganizationName = o.Name, + SsoAvailable = o.SsoConfigs.Any(sc => sc.Enabled), + SsoRequired = p != null && p.Enabled, + OrganizationIdentifier = o.Identifier, + VerifiedDate = od.VerifiedDate, + PolicyType = p.Type, + DomainName = od.DomainName + }) .AsNoTracking() .SingleOrDefaultAsync(); diff --git a/src/Sql/dbo/Stored Procedures/OrganizationDomainSsoDetails_ReadByEmail.sql b/src/Sql/dbo/Stored Procedures/OrganizationDomainSsoDetails_ReadByEmail.sql index 0a2964d655..b1e4ae7679 100644 --- a/src/Sql/dbo/Stored Procedures/OrganizationDomainSsoDetails_ReadByEmail.sql +++ b/src/Sql/dbo/Stored Procedures/OrganizationDomainSsoDetails_ReadByEmail.sql @@ -11,7 +11,7 @@ BEGIN SELECT O.Id AS OrganizationId, O.[Name] AS OrganizationName, - O.UseSso AS SsoAvailable, + S.Enabled AS SsoAvailable, P.Enabled AS SsoRequired, O.Identifier AS OrganizationIdentifier, OD.VerifiedDate, @@ -23,6 +23,8 @@ BEGIN ON O.Id = OD.OrganizationId LEFT JOIN [dbo].[PolicyView] P ON O.Id = P.OrganizationId + LEFT JOIN [dbo].[Ssoconfig] S + ON O.Id = S.OrganizationId WHERE OD.DomainName = @Domain AND O.Enabled = 1 AND (P.Id is NULL OR (P.Id IS NOT NULL AND P.[Type] = 4)) -- SSO Type diff --git a/util/Migrator/DbScripts/2023-02-16_FixSsoAvailableOrganizationDomain.sql b/util/Migrator/DbScripts/2023-02-16_FixSsoAvailableOrganizationDomain.sql new file mode 100644 index 0000000000..97a9a227a8 --- /dev/null +++ b/util/Migrator/DbScripts/2023-02-16_FixSsoAvailableOrganizationDomain.sql @@ -0,0 +1,31 @@ +CREATE OR ALTER PROCEDURE [dbo].[OrganizationDomainSsoDetails_ReadByEmail] + @Email NVARCHAR(256) +AS +BEGIN + SET NOCOUNT ON + + DECLARE @Domain NVARCHAR(256) + +SELECT @Domain = SUBSTRING(@Email, CHARINDEX( '@', @Email) + 1, LEN(@Email)) + +SELECT + O.Id AS OrganizationId, + O.[Name] AS OrganizationName, + S.Enabled AS SsoAvailable, + P.Enabled AS SsoRequired, + O.Identifier AS OrganizationIdentifier, + OD.VerifiedDate, + P.[Type] AS PolicyType, + OD.DomainName +FROM + [dbo].[OrganizationView] O + INNER JOIN [dbo].[OrganizationDomainView] OD +ON O.Id = OD.OrganizationId + LEFT JOIN [dbo].[PolicyView] P + ON O.Id = P.OrganizationId + LEFT JOIN [dbo].[Ssoconfig] S + ON O.Id = S.OrganizationId +WHERE OD.DomainName = @Domain + AND O.Enabled = 1 + AND (P.Id is NULL OR (P.Id IS NOT NULL AND P.[Type] = 4)) -- SSO Type +END \ No newline at end of file From 16bdd67cad8af35c2cb2aac39a7f2c012693314f Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Mon, 20 Feb 2023 13:01:49 -0500 Subject: [PATCH 36/49] SM-281: Secrets Manager Trash (#2688) --- .../Commands/Trash/EmptyTrashCommand.cs | 28 +++ .../Commands/Trash/RestoreTrashCommand.cs | 28 +++ .../SecretsManagerCollectionExtensions.cs | 4 + .../Repositories/SecretRepository.cs | 47 +++- .../Trash/EmptyTrashCommandTests.cs | 48 ++++ .../Trash/RestoreTrashCommandTests.cs | 48 ++++ .../Controllers/SecretsController.cs | 1 + .../Controllers/SecretsTrashController.cs | 80 ++++++ .../Commands/Trash/IEmptyTrashCommand.cs | 7 + .../Commands/Trash/IRestoreTrashCommand.cs | 6 + .../Repositories/ISecretRepository.cs | 3 + .../Controllers/SecretsTrashControllerTest.cs | 227 ++++++++++++++++++ 12 files changed, 525 insertions(+), 2 deletions(-) create mode 100644 bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Trash/EmptyTrashCommand.cs create mode 100644 bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Trash/RestoreTrashCommand.cs create mode 100644 bitwarden_license/test/Commercial.Core.Test/SecretsManager/Trash/EmptyTrashCommandTests.cs create mode 100644 bitwarden_license/test/Commercial.Core.Test/SecretsManager/Trash/RestoreTrashCommandTests.cs create mode 100644 src/Api/SecretsManager/Controllers/SecretsTrashController.cs create mode 100644 src/Core/SecretsManager/Commands/Trash/IEmptyTrashCommand.cs create mode 100644 src/Core/SecretsManager/Commands/Trash/IRestoreTrashCommand.cs create mode 100644 test/Api.IntegrationTest/SecretsManager/Controllers/SecretsTrashControllerTest.cs diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Trash/EmptyTrashCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Trash/EmptyTrashCommand.cs new file mode 100644 index 0000000000..9951d05752 --- /dev/null +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Trash/EmptyTrashCommand.cs @@ -0,0 +1,28 @@ +using Bit.Core.Exceptions; +using Bit.Core.SecretsManager.Commands.Trash.Interfaces; +using Bit.Core.SecretsManager.Repositories; + +namespace Bit.Commercial.Core.SecretsManager.Commands.Trash; + +public class EmptyTrashCommand : IEmptyTrashCommand +{ + private readonly ISecretRepository _secretRepository; + + public EmptyTrashCommand(ISecretRepository secretRepository) + { + _secretRepository = secretRepository; + } + + public async Task EmptyTrash(Guid organizationId, List ids) + { + var secrets = await _secretRepository.GetManyByOrganizationIdInTrashByIdsAsync(organizationId, ids); + + var missingId = ids.Except(secrets.Select(_ => _.Id)).Any(); + if (missingId) + { + throw new NotFoundException(); + } + + await _secretRepository.HardDeleteManyByIdAsync(ids); + } +} diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Trash/RestoreTrashCommand.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Trash/RestoreTrashCommand.cs new file mode 100644 index 0000000000..ad0b5bb7a2 --- /dev/null +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Commands/Trash/RestoreTrashCommand.cs @@ -0,0 +1,28 @@ +using Bit.Core.Exceptions; +using Bit.Core.SecretsManager.Commands.Trash.Interfaces; +using Bit.Core.SecretsManager.Repositories; + +namespace Bit.Commercial.Core.SecretsManager.Commands.Trash; + +public class RestoreTrashCommand : IRestoreTrashCommand +{ + private readonly ISecretRepository _secretRepository; + + public RestoreTrashCommand(ISecretRepository secretRepository) + { + _secretRepository = secretRepository; + } + + public async Task RestoreTrash(Guid organizationId, List ids) + { + var secrets = await _secretRepository.GetManyByOrganizationIdInTrashByIdsAsync(organizationId, ids); + + var missingId = ids.Except(secrets.Select(_ => _.Id)).Any(); + if (missingId) + { + throw new NotFoundException(); + } + + await _secretRepository.RestoreManyByIdAsync(ids); + } +} diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs index 429833089f..3f871d8532 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/SecretsManagerCollectionExtensions.cs @@ -4,12 +4,14 @@ using Bit.Commercial.Core.SecretsManager.Commands.Porting; using Bit.Commercial.Core.SecretsManager.Commands.Projects; using Bit.Commercial.Core.SecretsManager.Commands.Secrets; using Bit.Commercial.Core.SecretsManager.Commands.ServiceAccounts; +using Bit.Commercial.Core.SecretsManager.Commands.Trash; using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces; using Bit.Core.SecretsManager.Commands.AccessTokens.Interfaces; using Bit.Core.SecretsManager.Commands.Porting.Interfaces; using Bit.Core.SecretsManager.Commands.Projects.Interfaces; using Bit.Core.SecretsManager.Commands.Secrets.Interfaces; using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces; +using Bit.Core.SecretsManager.Commands.Trash.Interfaces; using Microsoft.Extensions.DependencyInjection; namespace Bit.Commercial.Core.SecretsManager; @@ -32,5 +34,7 @@ public static class SecretsManagerCollectionExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } } diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs index 43f1adb591..e08f89efbe 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretRepository.cs @@ -8,7 +8,6 @@ using Bit.Infrastructure.EntityFramework.SecretsManager.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; - namespace Bit.Commercial.Infrastructure.EntityFramework.SecretsManager.Repositories; public class SecretRepository : Repository, ISecretRepository @@ -72,6 +71,36 @@ public class SecretRepository : Repository>(secrets); } + public async Task> GetManyByOrganizationIdInTrashByIdsAsync(Guid organizationId, IEnumerable ids) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var secrets = await dbContext.Secret + .Where(s => ids.Contains(s.Id) && s.OrganizationId == organizationId && s.DeletedDate != null) + .Include("Projects") + .OrderBy(c => c.RevisionDate) + .ToListAsync(); + + return Mapper.Map>(secrets); + } + } + + public async Task> GetManyByOrganizationIdInTrashAsync(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var secrets = await dbContext.Secret + .Where(c => c.OrganizationId == organizationId && c.DeletedDate != null) + .Include("Projects") + .OrderBy(c => c.RevisionDate) + .ToListAsync(); + + return Mapper.Map>(secrets); + } + } + public async Task> GetManyByProjectIdAsync(Guid projectId, Guid userId, AccessClientType accessType) { using (var scope = ServiceScopeFactory.CreateScope()) @@ -168,7 +197,6 @@ public class SecretRepository : Repository ids.Contains(c.Id)); await secrets.ForEachAsync(secret => { @@ -179,6 +207,21 @@ public class SecretRepository : Repository ids) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var secrets = dbContext.Secret.Where(c => ids.Contains(c.Id)); + await secrets.ForEachAsync(secret => + { + dbContext.Attach(secret); + secret.DeletedDate = null; + }); + await dbContext.SaveChangesAsync(); + } + } + public async Task> ImportAsync(IEnumerable secrets) { using (var scope = ServiceScopeFactory.CreateScope()) diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Trash/EmptyTrashCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Trash/EmptyTrashCommandTests.cs new file mode 100644 index 0000000000..603e5fcf4a --- /dev/null +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Trash/EmptyTrashCommandTests.cs @@ -0,0 +1,48 @@ +using Bit.Commercial.Core.SecretsManager.Commands.Trash; +using Bit.Core.Exceptions; +using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Commercial.Core.Test.SecretsManager.Trash; + +[SutProviderCustomize] +[ProjectCustomize] +public class EmptyTrashCommandTests +{ + [Theory] + [BitAutoData] + public async Task EmptyTrash_Throws_NotFoundException(Guid orgId, Secret s1, Secret s2, SutProvider sutProvider) + { + s1.DeletedDate = DateTime.Now; + + var ids = new List { s1.Id, s2.Id }; + sutProvider.GetDependency() + .GetManyByOrganizationIdInTrashByIdsAsync(orgId, ids) + .Returns(new List { s1 }); + + await Assert.ThrowsAsync(() => sutProvider.Sut.EmptyTrash(orgId, ids)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().RestoreManyByIdAsync(default); + } + + [Theory] + [BitAutoData] + public async Task EmptyTrash_Success(Guid orgId, Secret s1, Secret s2, SutProvider sutProvider) + { + s1.DeletedDate = DateTime.Now; + + var ids = new List { s1.Id, s2.Id }; + sutProvider.GetDependency() + .GetManyByOrganizationIdInTrashByIdsAsync(orgId, ids) + .Returns(new List { s1, s2 }); + + await sutProvider.Sut.EmptyTrash(orgId, ids); + + await sutProvider.GetDependency().Received(1).HardDeleteManyByIdAsync(ids); + } +} diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Trash/RestoreTrashCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Trash/RestoreTrashCommandTests.cs new file mode 100644 index 0000000000..1054ad154f --- /dev/null +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Trash/RestoreTrashCommandTests.cs @@ -0,0 +1,48 @@ +using Bit.Commercial.Core.SecretsManager.Commands.Trash; +using Bit.Core.Exceptions; +using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Repositories; +using Bit.Core.Test.SecretsManager.AutoFixture.ProjectsFixture; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Commercial.Core.Test.SecretsManager.Trash; + +[SutProviderCustomize] +[ProjectCustomize] +public class RestoreTrashCommandTests +{ + [Theory] + [BitAutoData] + public async Task RestoreTrash_Throws_NotFoundException(Guid orgId, Secret s1, Secret s2, SutProvider sutProvider) + { + s1.DeletedDate = DateTime.Now; + + var ids = new List { s1.Id, s2.Id }; + sutProvider.GetDependency() + .GetManyByOrganizationIdInTrashByIdsAsync(orgId, ids) + .Returns(new List { s1 }); + + await Assert.ThrowsAsync(() => sutProvider.Sut.RestoreTrash(orgId, ids)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().RestoreManyByIdAsync(default); + } + + [Theory] + [BitAutoData] + public async Task RestoreTrash_Success(Guid orgId, Secret s1, Secret s2, SutProvider sutProvider) + { + s1.DeletedDate = DateTime.Now; + + var ids = new List { s1.Id, s2.Id }; + sutProvider.GetDependency() + .GetManyByOrganizationIdInTrashByIdsAsync(orgId, ids) + .Returns(new List { s1, s2 }); + + await sutProvider.Sut.RestoreTrash(orgId, ids); + + await sutProvider.GetDependency().Received(1).RestoreManyByIdAsync(ids); + } +} diff --git a/src/Api/SecretsManager/Controllers/SecretsController.cs b/src/Api/SecretsManager/Controllers/SecretsController.cs index 7418fe0a17..5c91c2e807 100644 --- a/src/Api/SecretsManager/Controllers/SecretsController.cs +++ b/src/Api/SecretsManager/Controllers/SecretsController.cs @@ -70,6 +70,7 @@ public class SecretsController : Controller public async Task GetAsync([FromRoute] Guid id) { var secret = await _secretRepository.GetByIdAsync(id); + if (secret == null || !_currentContext.AccessSecretsManager(secret.OrganizationId)) { throw new NotFoundException(); diff --git a/src/Api/SecretsManager/Controllers/SecretsTrashController.cs b/src/Api/SecretsManager/Controllers/SecretsTrashController.cs new file mode 100644 index 0000000000..6f0b65d458 --- /dev/null +++ b/src/Api/SecretsManager/Controllers/SecretsTrashController.cs @@ -0,0 +1,80 @@ +using Bit.Api.SecretsManager.Models.Response; +using Bit.Core.Context; +using Bit.Core.Exceptions; +using Bit.Core.SecretsManager.Commands.Trash.Interfaces; +using Bit.Core.SecretsManager.Repositories; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.Api.SecretsManager.Controllers; + +[SecretsManager] +[Authorize("secrets")] +public class TrashController : Controller +{ + private readonly ICurrentContext _currentContext; + private readonly ISecretRepository _secretRepository; + private readonly IEmptyTrashCommand _emptyTrashCommand; + private readonly IRestoreTrashCommand _restoreTrashCommand; + + public TrashController( + ICurrentContext currentContext, + ISecretRepository secretRepository, + IEmptyTrashCommand emptyTrashCommand, + IRestoreTrashCommand restoreTrashCommand) + { + _currentContext = currentContext; + _secretRepository = secretRepository; + _emptyTrashCommand = emptyTrashCommand; + _restoreTrashCommand = restoreTrashCommand; + } + + [HttpGet("secrets/{organizationId}/trash")] + public async Task ListByOrganizationAsync(Guid organizationId) + { + if (!_currentContext.AccessSecretsManager(organizationId)) + { + throw new NotFoundException(); + } + + if (!await _currentContext.OrganizationAdmin(organizationId)) + { + throw new UnauthorizedAccessException(); + } + + var secrets = await _secretRepository.GetManyByOrganizationIdInTrashAsync(organizationId); + return new SecretWithProjectsListResponseModel(secrets); + } + + [HttpPost("secrets/{organizationId}/trash/empty")] + public async Task EmptyTrashAsync(Guid organizationId, [FromBody] List ids) + { + if (!_currentContext.AccessSecretsManager(organizationId)) + { + throw new NotFoundException(); + } + + if (!await _currentContext.OrganizationAdmin(organizationId)) + { + throw new UnauthorizedAccessException(); + } + + await _emptyTrashCommand.EmptyTrash(organizationId, ids); + } + + [HttpPost("secrets/{organizationId}/trash/restore")] + public async Task RestoreTrashAsync(Guid organizationId, [FromBody] List ids) + { + if (!_currentContext.AccessSecretsManager(organizationId)) + { + throw new NotFoundException(); + } + + if (!await _currentContext.OrganizationAdmin(organizationId)) + { + throw new UnauthorizedAccessException(); + } + + await _restoreTrashCommand.RestoreTrash(organizationId, ids); + } +} diff --git a/src/Core/SecretsManager/Commands/Trash/IEmptyTrashCommand.cs b/src/Core/SecretsManager/Commands/Trash/IEmptyTrashCommand.cs new file mode 100644 index 0000000000..74c5c21a0a --- /dev/null +++ b/src/Core/SecretsManager/Commands/Trash/IEmptyTrashCommand.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.SecretsManager.Commands.Trash.Interfaces; + +public interface IEmptyTrashCommand +{ + Task EmptyTrash(Guid organizationId, List ids); +} + diff --git a/src/Core/SecretsManager/Commands/Trash/IRestoreTrashCommand.cs b/src/Core/SecretsManager/Commands/Trash/IRestoreTrashCommand.cs new file mode 100644 index 0000000000..561b1ffd82 --- /dev/null +++ b/src/Core/SecretsManager/Commands/Trash/IRestoreTrashCommand.cs @@ -0,0 +1,6 @@ +namespace Bit.Core.SecretsManager.Commands.Trash.Interfaces; + +public interface IRestoreTrashCommand +{ + Task RestoreTrash(Guid organizationId, List ids); +} diff --git a/src/Core/SecretsManager/Repositories/ISecretRepository.cs b/src/Core/SecretsManager/Repositories/ISecretRepository.cs index 8bc9f8eb6e..bfc59fed6a 100644 --- a/src/Core/SecretsManager/Repositories/ISecretRepository.cs +++ b/src/Core/SecretsManager/Repositories/ISecretRepository.cs @@ -6,6 +6,8 @@ namespace Bit.Core.SecretsManager.Repositories; public interface ISecretRepository { Task> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType); + Task> GetManyByOrganizationIdInTrashAsync(Guid organizationId); + Task> GetManyByOrganizationIdInTrashByIdsAsync(Guid organizationId, IEnumerable ids); Task> GetManyByIds(IEnumerable ids); Task> GetManyByProjectIdAsync(Guid projectId, Guid userId, AccessClientType accessType); Task GetByIdAsync(Guid id); @@ -13,5 +15,6 @@ public interface ISecretRepository Task UpdateAsync(Secret secret); Task SoftDeleteManyByIdAsync(IEnumerable ids); Task HardDeleteManyByIdAsync(IEnumerable ids); + Task RestoreManyByIdAsync(IEnumerable ids); Task> ImportAsync(IEnumerable secrets); } diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsTrashControllerTest.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsTrashControllerTest.cs new file mode 100644 index 0000000000..97dd827a8c --- /dev/null +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsTrashControllerTest.cs @@ -0,0 +1,227 @@ +using System.Net; +using System.Net.Http.Headers; +using Bit.Api.IntegrationTest.Factories; +using Bit.Api.SecretsManager.Models.Response; +using Bit.Core.Enums; +using Bit.Core.SecretsManager.Repositories; +using Xunit; +using Secret = Bit.Core.SecretsManager.Entities.Secret; + +namespace Bit.Api.IntegrationTest.SecretsManager.Controllers; + +public class SecretsTrashControllerTest : IClassFixture, IAsyncLifetime +{ + private readonly string _mockEncryptedString = + "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98sp4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg="; + + private readonly HttpClient _client; + private readonly ApiApplicationFactory _factory; + private readonly ISecretRepository _secretRepository; + + private string _email = null!; + private SecretsManagerOrganizationHelper _organizationHelper = null!; + + public SecretsTrashControllerTest(ApiApplicationFactory factory) + { + _factory = factory; + _client = _factory.CreateClient(); + _secretRepository = _factory.GetService(); + } + + public async Task InitializeAsync() + { + _email = $"integration-test{Guid.NewGuid()}@bitwarden.com"; + await _factory.LoginWithNewAccount(_email); + _organizationHelper = new SecretsManagerOrganizationHelper(_factory, _email); + } + + public Task DisposeAsync() + { + _client.Dispose(); + return Task.CompletedTask; + } + + private async Task LoginAsync(string email) + { + var tokens = await _factory.LoginAsync(email); + _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task ListByOrganization_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + + var response = await _client.GetAsync($"/secrets/{org.Id}/trash"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task ListByOrganization_NotAdmin_Unauthorized() + { + var (org, _) = await _organizationHelper.Initialize(true, true); + var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var response = await _client.GetAsync($"/secrets/{org.Id}/trash"); + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + } + + [Fact] + public async Task ListByOrganization_Success() + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + + await _secretRepository.CreateAsync(new Secret + { + OrganizationId = org.Id, + Key = _mockEncryptedString, + Value = _mockEncryptedString, + DeletedDate = DateTime.Now, + }); + + await _secretRepository.CreateAsync(new Secret + { + OrganizationId = org.Id, + Key = _mockEncryptedString, + Value = _mockEncryptedString, + }); + + var response = await _client.GetAsync($"/secrets/{org.Id}/trash"); + + response.EnsureSuccessStatusCode(); + var result = await response.Content.ReadFromJsonAsync(); + Assert.Single(result!.Secrets); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task Empty_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + + var ids = new List { Guid.NewGuid() }; + var response = await _client.PostAsJsonAsync($"/secrets/{org.Id}/trash/empty", ids); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task Empty_NotAdmin_Unauthorized() + { + var (org, _) = await _organizationHelper.Initialize(true, true); + var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var ids = new List { Guid.NewGuid() }; + var response = await _client.PostAsJsonAsync($"/secrets/{org.Id}/trash/empty", ids); + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + } + + [Fact] + public async Task Empty_Invalid_NotFound() + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + + var secret = await _secretRepository.CreateAsync(new Secret + { + OrganizationId = org.Id, + Key = _mockEncryptedString, + Value = _mockEncryptedString + }); + + var ids = new List { secret.Id }; + var response = await _client.PostAsJsonAsync($"/secrets/{org.Id}/trash/empty", ids); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task Empty_Success() + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + + var secret = await _secretRepository.CreateAsync(new Secret + { + OrganizationId = org.Id, + Key = _mockEncryptedString, + Value = _mockEncryptedString, + DeletedDate = DateTime.Now, + }); + + var ids = new List { secret.Id }; + var response = await _client.PostAsJsonAsync($"/secrets/{org.Id}/trash/empty", ids); + response.EnsureSuccessStatusCode(); + } + + [Theory] + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + public async Task Restore_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets); + await LoginAsync(_email); + + var ids = new List { Guid.NewGuid() }; + var response = await _client.PostAsJsonAsync($"/secrets/{org.Id}/trash/restore", ids); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task Restore_NotAdmin_Unauthorized() + { + var (org, _) = await _organizationHelper.Initialize(true, true); + var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true); + await LoginAsync(email); + + var ids = new List { Guid.NewGuid() }; + var response = await _client.PostAsJsonAsync($"/secrets/{org.Id}/trash/restore", ids); + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + } + + [Fact] + public async Task Restore_Invalid_NotFound() + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + + var secret = await _secretRepository.CreateAsync(new Secret + { + OrganizationId = org.Id, + Key = _mockEncryptedString, + Value = _mockEncryptedString + }); + + var ids = new List { secret.Id }; + var response = await _client.PostAsJsonAsync($"/secrets/{org.Id}/trash/restore", ids); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task Restore_Success() + { + var (org, _) = await _organizationHelper.Initialize(true, true); + await LoginAsync(_email); + + var secret = await _secretRepository.CreateAsync(new Secret + { + OrganizationId = org.Id, + Key = _mockEncryptedString, + Value = _mockEncryptedString, + DeletedDate = DateTime.Now, + }); + + var ids = new List { secret.Id }; + var response = await _client.PostAsJsonAsync($"/secrets/{org.Id}/trash/restore", ids); + response.EnsureSuccessStatusCode(); + } +} From 146d5b19848603489127526e8a4180e62cbbbbe1 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 21 Feb 2023 18:24:49 +0100 Subject: [PATCH 37/49] [SM-396] Self-enroll Secrets Manager (#2671) * Add endpoint for self enrolling in secrets manager * Add SecretsManager attribute * Mark endpoint as only cloud, enable secrets manager for the current user * Remove response --- .../Controllers/OrganizationsController.cs | 31 +++++++++++++++++++ ...izationEnrollSecretsManagerRequestModel.cs | 6 ++++ 2 files changed, 37 insertions(+) create mode 100644 src/Api/Models/Request/Organizations/OrganizationEnrollSecretsManagerRequestModel.cs diff --git a/src/Api/Controllers/OrganizationsController.cs b/src/Api/Controllers/OrganizationsController.cs index f616d65292..57c39359ba 100644 --- a/src/Api/Controllers/OrganizationsController.cs +++ b/src/Api/Controllers/OrganizationsController.cs @@ -4,6 +4,7 @@ using Bit.Api.Models.Request.Accounts; using Bit.Api.Models.Request.Organizations; using Bit.Api.Models.Response; using Bit.Api.Models.Response.Organizations; +using Bit.Api.SecretsManager; using Bit.Api.Utilities; using Bit.Core.Context; using Bit.Core.Enums; @@ -716,4 +717,34 @@ public class OrganizationsController : Controller return new OrganizationSsoResponseModel(organization, _globalSettings, ssoConfig); } + + // This is a temporary endpoint to self-enroll in secrets manager + [SecretsManager] + [SelfHosted(NotSelfHostedOnly = true)] + [HttpPost("{id}/enroll-secrets-manager")] + public async Task EnrollSecretsManager(Guid id, [FromBody] OrganizationEnrollSecretsManagerRequestModel model) + { + var userId = _userService.GetProperUserId(User).Value; + if (!await _currentContext.OrganizationAdmin(id)) + { + throw new NotFoundException(); + } + + var organization = await _organizationRepository.GetByIdAsync(id); + if (organization == null) + { + throw new NotFoundException(); + } + + organization.UseSecretsManager = model.Enabled; + await _organizationService.UpdateAsync(organization); + + // Turn on Secrets Manager for the user + if (model.Enabled) + { + var orgUser = await _organizationUserRepository.GetByOrganizationAsync(id, userId); + orgUser.AccessSecretsManager = true; + await _organizationUserRepository.ReplaceAsync(orgUser); + } + } } diff --git a/src/Api/Models/Request/Organizations/OrganizationEnrollSecretsManagerRequestModel.cs b/src/Api/Models/Request/Organizations/OrganizationEnrollSecretsManagerRequestModel.cs new file mode 100644 index 0000000000..7befaa25c6 --- /dev/null +++ b/src/Api/Models/Request/Organizations/OrganizationEnrollSecretsManagerRequestModel.cs @@ -0,0 +1,6 @@ +namespace Bit.Api.Models.Request.Organizations; + +public class OrganizationEnrollSecretsManagerRequestModel +{ + public bool Enabled { get; set; } +} From 7365ca09250c906843f83d6b5332bf3320319c38 Mon Sep 17 00:00:00 2001 From: Brandon Maharaj Date: Tue, 21 Feb 2023 15:59:50 -0500 Subject: [PATCH 38/49] [SG-783] Change organization user list to pull the user's selected color and display it in the avatar (#2630) * work: baseline for org stuff * fix: missed view as usual oops * fix: refresh using sp_refreshsqlmodule --- .../OrganizationUserResponseModel.cs | 3 ++ .../OrganizationUserUserDetails.cs | 1 + .../Queries/OrganizationUserUserViewQuery.cs | 1 + .../Views/OrganizationUserUserDetailsView.sql | 1 + ...-01-23_00_AddAvatarToOrganizationUsers.sql | 32 +++++++++++++++++++ 5 files changed, 38 insertions(+) create mode 100644 util/Migrator/DbScripts/2023-01-23_00_AddAvatarToOrganizationUsers.sql diff --git a/src/Api/Models/Response/Organizations/OrganizationUserResponseModel.cs b/src/Api/Models/Response/Organizations/OrganizationUserResponseModel.cs index 13c1aaf658..1ce055a869 100644 --- a/src/Api/Models/Response/Organizations/OrganizationUserResponseModel.cs +++ b/src/Api/Models/Response/Organizations/OrganizationUserResponseModel.cs @@ -89,6 +89,7 @@ public class OrganizationUserUserDetailsResponseModel : OrganizationUserResponse Name = organizationUser.Name; Email = organizationUser.Email; + AvatarColor = organizationUser.AvatarColor; TwoFactorEnabled = twoFactorEnabled; SsoBound = !string.IsNullOrWhiteSpace(organizationUser.SsoExternalId); Collections = organizationUser.Collections.Select(c => new SelectionReadOnlyResponseModel(c)); @@ -97,8 +98,10 @@ public class OrganizationUserUserDetailsResponseModel : OrganizationUserResponse ResetPasswordEnrolled = ResetPasswordEnrolled && !organizationUser.UsesKeyConnector; } + public string Name { get; set; } public string Email { get; set; } + public string AvatarColor { get; set; } public bool TwoFactorEnabled { get; set; } public bool SsoBound { get; set; } public IEnumerable Collections { get; set; } diff --git a/src/Core/Models/Data/Organizations/OrganizationUsers/OrganizationUserUserDetails.cs b/src/Core/Models/Data/Organizations/OrganizationUsers/OrganizationUserUserDetails.cs index 0576904cd0..3bb9416813 100644 --- a/src/Core/Models/Data/Organizations/OrganizationUsers/OrganizationUserUserDetails.cs +++ b/src/Core/Models/Data/Organizations/OrganizationUsers/OrganizationUserUserDetails.cs @@ -12,6 +12,7 @@ public class OrganizationUserUserDetails : IExternal, ITwoFactorProvidersUser public Guid? UserId { get; set; } public string Name { get; set; } public string Email { get; set; } + public string AvatarColor { get; set; } public string TwoFactorProviders { get; set; } public bool? Premium { get; set; } public OrganizationUserStatusType Status { get; set; } diff --git a/src/Infrastructure.EntityFramework/Repositories/Queries/OrganizationUserUserViewQuery.cs b/src/Infrastructure.EntityFramework/Repositories/Queries/OrganizationUserUserViewQuery.cs index 49dc3a385d..2f36743dc0 100644 --- a/src/Infrastructure.EntityFramework/Repositories/Queries/OrganizationUserUserViewQuery.cs +++ b/src/Infrastructure.EntityFramework/Repositories/Queries/OrganizationUserUserViewQuery.cs @@ -19,6 +19,7 @@ public class OrganizationUserUserDetailsViewQuery : IQuery Date: Wed, 22 Feb 2023 11:21:07 +0100 Subject: [PATCH 39/49] [SM-504] Fix service account not accessing secrets (#2709) --- src/Core/Context/CurrentContext.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Core/Context/CurrentContext.cs b/src/Core/Context/CurrentContext.cs index 4411509cd5..598f30bbb2 100644 --- a/src/Core/Context/CurrentContext.cs +++ b/src/Core/Context/CurrentContext.cs @@ -35,6 +35,7 @@ public class CurrentContext : ICurrentContext public virtual string ClientId { get; set; } public virtual Version ClientVersion { get; set; } public virtual ClientType ClientType { get; set; } + public virtual Guid? ServiceAccountOrganizationId { get; set; } public CurrentContext(IProviderUserRepository providerUserRepository) { @@ -146,6 +147,11 @@ public class CurrentContext : ICurrentContext ClientType = c; } + if (ClientType == ClientType.ServiceAccount) + { + ServiceAccountOrganizationId = new Guid(GetClaimValue(claimsDict, Claims.Organization)); + } + DeviceIdentifier = GetClaimValue(claimsDict, Claims.Device); Organizations = GetOrganizations(claimsDict, orgApi); @@ -445,6 +451,11 @@ public class CurrentContext : ICurrentContext public bool AccessSecretsManager(Guid orgId) { + if (ServiceAccountOrganizationId.HasValue && ServiceAccountOrganizationId.Value == orgId) + { + return true; + } + return Organizations?.Any(o => o.Id == orgId && o.AccessSecretsManager) ?? false; } From cf13ee71f3f74958317cdeb7d9a05aa6705bb1e0 Mon Sep 17 00:00:00 2001 From: Matt Bishop Date: Wed, 22 Feb 2023 14:18:32 -0500 Subject: [PATCH 40/49] Upgrade DBUp to shed System.Data.SqlClient dependency (#2730) --- src/Admin/packages.lock.json | 218 +++++++++++++++++-------------- util/Migrator/Migrator.csproj | 2 +- util/Migrator/packages.lock.json | 205 +++++++++++++++++------------ util/Setup/packages.lock.json | 207 ++++++++++++++++------------- 4 files changed, 358 insertions(+), 274 deletions(-) diff --git a/src/Admin/packages.lock.json b/src/Admin/packages.lock.json index cf58d395fe..ca31e68892 100644 --- a/src/Admin/packages.lock.json +++ b/src/Admin/packages.lock.json @@ -182,21 +182,21 @@ }, "dbup-core": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "CR00QMAtHjfeMhwxFC5haoA0q4KZ5s6Y/AdZaT6oFjySik2eFEqVasuLgWSPKSiR7ti3z01BtiR7aD3nVckAsg==", + "resolved": "5.0.8", + "contentHash": "d+3RxJDftcarp1Y7jI78HRdRWRC7VFjM+rB2CFHWDmao6OixuLrqiyEo1DeuMNrWLTR5mmE8p1YTpFOvozI9ZQ==", "dependencies": { - "Microsoft.CSharp": "4.4.0", + "Microsoft.CSharp": "4.7.0", "System.Diagnostics.TraceSource": "4.3.0" } }, "dbup-sqlserver": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "/4hy4qmbWmtbLJGq8XCH3mtlgMld2G8rbXcjNDhqkq5y6dGZDW03OI4UsnQRxBiTQD5aYOcLuycK1dCJYhkdSw==", + "resolved": "5.0.8", + "contentHash": "b954l5Zgj9qgHtm16SLq2qGLJ0gIZtrWdh6JHoUsCLMHYW+0K2Oevabquw447At4U6X2t4CNuy7ZLHYf/Z/8yg==", "dependencies": { - "Microsoft.Azure.Services.AppAuthentication": "1.3.1", - "System.Data.SqlClient": "4.6.0", - "dbup-core": "4.5.0" + "Microsoft.Azure.Services.AppAuthentication": "1.6.2", + "Microsoft.Data.SqlClient": "5.0.1", + "dbup-core": "5.0.8" } }, "DnsClient": { @@ -502,10 +502,10 @@ }, "Microsoft.Azure.Services.AppAuthentication": { "type": "Transitive", - "resolved": "1.3.1", - "contentHash": "59CEcmUSlg5nYOzcyhdoUu+EQH4wrjCKj7dNuuPMeIjDCikAON9/KQXTQLfzfWTjDwqHIRptAAj0DTBp25lFcg==", + "resolved": "1.6.2", + "contentHash": "rSQhTv43ionr9rWvE4vxIe/i73XR5hoBYfh7UUgdaVOGW1MZeikR9RmgaJhonTylimCcCuJvrU0zXsSIFOsTGw==", "dependencies": { - "Microsoft.IdentityModel.Clients.ActiveDirectory": "4.3.0", + "Microsoft.IdentityModel.Clients.ActiveDirectory": "5.2.9", "System.Diagnostics.Process": "4.3.0" } }, @@ -1089,15 +1089,22 @@ }, "Microsoft.IdentityModel.Clients.ActiveDirectory": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "IRXnTCHxwnpnGBHVnTWd8RBJk7nsBsNZVl8j20kh234bP+oBILkt+6Iw5vQg5Q+sZmALt3Oq6X3Kx7qY71XyVw==", + "resolved": "5.2.9", + "contentHash": "WhBAG/9hWiMHIXve4ZgwXP3spRwf7kFFfejf76QA5BvumgnPp8iDkDCiJugzAcpW1YaHB526z1UVxHhVT1E5qw==", "dependencies": { + "Microsoft.CSharp": "4.3.0", "NETStandard.Library": "1.6.1", + "System.ComponentModel.TypeConverter": "4.3.0", + "System.Dynamic.Runtime": "4.3.0", + "System.Net.Http": "4.3.4", + "System.Private.Uri": "4.3.2", + "System.Runtime.Serialization.Formatters": "4.3.0", "System.Runtime.Serialization.Json": "4.3.0", "System.Runtime.Serialization.Primitives": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Security.SecureString": "4.3.0", - "System.Xml.XDocument": "4.3.0" + "System.Xml.XDocument": "4.3.0", + "System.Xml.XmlDocument": "4.3.0" } }, "Microsoft.IdentityModel.JsonWebTokens": { @@ -1521,16 +1528,6 @@ "Microsoft.NETCore.Targets": "1.1.0" } }, - "runtime.native.System.Data.SqlClient.sni": { - "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "AJfX7owAAkMjWQYhoml5IBfXh8UyYPjktn8pK0BFGAdKgBS7HqMz1fw5vdzfZUWfhtTPDGCjgNttt46ZyEmSjg==", - "dependencies": { - "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0", - "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0", - "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0" - } - }, "runtime.native.System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", @@ -1623,21 +1620,6 @@ "resolved": "4.3.2", "contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg==" }, - "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": { - "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg==" - }, - "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": { - "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ==" - }, - "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": { - "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA==" - }, "SendGrid": { "type": "Transitive", "resolved": "9.27.0", @@ -1912,29 +1894,69 @@ }, "System.Collections.NonGeneric": { "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "hMxFT2RhhlffyCdKLDXjx8WEC5JfCvNozAZxCablAuFRH74SCV4AgzE8yJCh/73bFnEoZgJ9MJmkjQ0dJmnKqA==", + "resolved": "4.3.0", + "contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", "dependencies": { - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" } }, "System.Collections.Specialized": { "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "/HKQyVP0yH1I0YtK7KJL/28snxHNH/bi+0lgk/+MbURF6ULhAE31MDI+NZDerNWu264YbxklXCCygISgm+HMug==", + "resolved": "4.3.0", + "contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==", "dependencies": { - "System.Collections.NonGeneric": "4.0.1", - "System.Globalization": "4.0.11", - "System.Globalization.Extensions": "4.0.1", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" + "System.Collections.NonGeneric": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Extensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.ComponentModel": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.ComponentModel.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==", + "dependencies": { + "System.ComponentModel": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.ComponentModel.TypeConverter": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Collections.NonGeneric": "4.3.0", + "System.Collections.Specialized": "4.3.0", + "System.ComponentModel": "4.3.0", + "System.ComponentModel.Primitives": "4.3.0", + "System.Globalization": "4.3.0", + "System.Linq": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Reflection.TypeExtensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" } }, "System.Composition": { @@ -2054,17 +2076,6 @@ "System.Text.Encoding": "4.3.0" } }, - "System.Data.SqlClient": { - "type": "Transitive", - "resolved": "4.6.0", - "contentHash": "gwItUWW1BMCckicFO85c8frFaMK8SGqYn5IeA3GSX4Lmid+CjXETfoHz7Uv+Vx6L0No7iRc/7cBL8gd6o9k9/g==", - "dependencies": { - "Microsoft.Win32.Registry": "4.5.0", - "System.Security.Principal.Windows": "4.5.0", - "System.Text.Encoding.CodePages": "4.5.0", - "runtime.native.System.Data.SqlClient.sni": "4.5.0" - } - }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.3.0", @@ -2168,24 +2179,23 @@ }, "System.Dynamic.Runtime": { "type": "Transitive", - "resolved": "4.0.11", - "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", + "resolved": "4.3.0", + "contentHash": "SNVi1E/vfWUAs/WYKhE9+qlS6KqK0YVhnlT0HQtr8pMIA8YX3lwy3uPMownDwdYISBdmAF/2holEIldVp85Wag==", "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.Linq": "4.1.0", - "System.Linq.Expressions": "4.1.0", - "System.ObjectModel": "4.0.12", - "System.Reflection": "4.1.0", - "System.Reflection.Emit": "4.0.1", - "System.Reflection.Emit.ILGeneration": "4.0.1", - "System.Reflection.Primitives": "4.0.1", - "System.Reflection.TypeExtensions": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Linq": "4.3.0", + "System.Linq.Expressions": "4.3.0", + "System.ObjectModel": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Emit": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Reflection.TypeExtensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" } }, "System.Formats.Asn1": { @@ -2816,6 +2826,18 @@ "System.Runtime.Extensions": "4.3.0" } }, + "System.Runtime.Serialization.Formatters": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KT591AkTNFOTbhZlaeMVvfax3RqhH1EJlcwF50Wm7sfnBLuHiOeZRRKrr1ns3NESkM20KPZ5Ol/ueMq5vg4QoQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Serialization.Primitives": "4.3.0" + } + }, "System.Runtime.Serialization.Json": { "type": "Transitive", "resolved": "4.3.0", @@ -3307,7 +3329,7 @@ "commercial.core": { "type": "Project", "dependencies": { - "Core": "[2023.1.0, )" + "Core": "[2023.2.0, )" } }, "core": { @@ -3354,7 +3376,7 @@ "infrastructure.dapper": { "type": "Project", "dependencies": { - "Core": "[2023.1.0, )", + "Core": "[2023.2.0, )", "Dapper": "[2.0.123, )" } }, @@ -3362,7 +3384,7 @@ "type": "Project", "dependencies": { "AutoMapper.Extensions.Microsoft.DependencyInjection": "[11.0.0, )", - "Core": "[2023.1.0, )", + "Core": "[2023.2.0, )", "Microsoft.EntityFrameworkCore.Relational": "[6.0.12, )", "Microsoft.EntityFrameworkCore.SqlServer": "[6.0.12, )", "Microsoft.EntityFrameworkCore.Sqlite": "[6.0.12, )", @@ -3374,38 +3396,38 @@ "migrator": { "type": "Project", "dependencies": { - "Core": "[2023.1.0, )", + "Core": "[2023.2.0, )", "Microsoft.Extensions.Logging": "[6.0.0, )", - "dbup-sqlserver": "[4.5.0, )" + "dbup-sqlserver": "[5.0.8, )" } }, "mysqlmigrations": { "type": "Project", "dependencies": { - "Core": "[2023.1.0, )", - "Infrastructure.EntityFramework": "[2023.1.0, )" + "Core": "[2023.2.0, )", + "Infrastructure.EntityFramework": "[2023.2.0, )" } }, "postgresmigrations": { "type": "Project", "dependencies": { - "Core": "[2023.1.0, )", - "Infrastructure.EntityFramework": "[2023.1.0, )" + "Core": "[2023.2.0, )", + "Infrastructure.EntityFramework": "[2023.2.0, )" } }, "sharedweb": { "type": "Project", "dependencies": { - "Core": "[2023.1.0, )", - "Infrastructure.Dapper": "[2023.1.0, )", - "Infrastructure.EntityFramework": "[2023.1.0, )" + "Core": "[2023.2.0, )", + "Infrastructure.Dapper": "[2023.2.0, )", + "Infrastructure.EntityFramework": "[2023.2.0, )" } }, "sqlitemigrations": { "type": "Project", "dependencies": { - "Core": "[2023.1.0, )", - "Infrastructure.EntityFramework": "[2023.1.0, )" + "Core": "[2023.2.0, )", + "Infrastructure.EntityFramework": "[2023.2.0, )" } } } diff --git a/util/Migrator/Migrator.csproj b/util/Migrator/Migrator.csproj index 954db4a618..c7f00465dd 100644 --- a/util/Migrator/Migrator.csproj +++ b/util/Migrator/Migrator.csproj @@ -5,7 +5,7 @@ - + diff --git a/util/Migrator/packages.lock.json b/util/Migrator/packages.lock.json index a55fa2a08c..3f4398b3a3 100644 --- a/util/Migrator/packages.lock.json +++ b/util/Migrator/packages.lock.json @@ -4,13 +4,13 @@ "net6.0": { "dbup-sqlserver": { "type": "Direct", - "requested": "[4.5.0, )", - "resolved": "4.5.0", - "contentHash": "/4hy4qmbWmtbLJGq8XCH3mtlgMld2G8rbXcjNDhqkq5y6dGZDW03OI4UsnQRxBiTQD5aYOcLuycK1dCJYhkdSw==", + "requested": "[5.0.8, )", + "resolved": "5.0.8", + "contentHash": "b954l5Zgj9qgHtm16SLq2qGLJ0gIZtrWdh6JHoUsCLMHYW+0K2Oevabquw447At4U6X2t4CNuy7ZLHYf/Z/8yg==", "dependencies": { - "Microsoft.Azure.Services.AppAuthentication": "1.3.1", - "System.Data.SqlClient": "4.6.0", - "dbup-core": "4.5.0" + "Microsoft.Azure.Services.AppAuthentication": "1.6.2", + "Microsoft.Data.SqlClient": "5.0.1", + "dbup-core": "5.0.8" } }, "Microsoft.Extensions.Logging": { @@ -152,10 +152,10 @@ }, "dbup-core": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "CR00QMAtHjfeMhwxFC5haoA0q4KZ5s6Y/AdZaT6oFjySik2eFEqVasuLgWSPKSiR7ti3z01BtiR7aD3nVckAsg==", + "resolved": "5.0.8", + "contentHash": "d+3RxJDftcarp1Y7jI78HRdRWRC7VFjM+rB2CFHWDmao6OixuLrqiyEo1DeuMNrWLTR5mmE8p1YTpFOvozI9ZQ==", "dependencies": { - "Microsoft.CSharp": "4.4.0", + "Microsoft.CSharp": "4.7.0", "System.Diagnostics.TraceSource": "4.3.0" } }, @@ -430,10 +430,10 @@ }, "Microsoft.Azure.Services.AppAuthentication": { "type": "Transitive", - "resolved": "1.3.1", - "contentHash": "59CEcmUSlg5nYOzcyhdoUu+EQH4wrjCKj7dNuuPMeIjDCikAON9/KQXTQLfzfWTjDwqHIRptAAj0DTBp25lFcg==", + "resolved": "1.6.2", + "contentHash": "rSQhTv43ionr9rWvE4vxIe/i73XR5hoBYfh7UUgdaVOGW1MZeikR9RmgaJhonTylimCcCuJvrU0zXsSIFOsTGw==", "dependencies": { - "Microsoft.IdentityModel.Clients.ActiveDirectory": "4.3.0", + "Microsoft.IdentityModel.Clients.ActiveDirectory": "5.2.9", "System.Diagnostics.Process": "4.3.0" } }, @@ -723,15 +723,22 @@ }, "Microsoft.IdentityModel.Clients.ActiveDirectory": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "IRXnTCHxwnpnGBHVnTWd8RBJk7nsBsNZVl8j20kh234bP+oBILkt+6Iw5vQg5Q+sZmALt3Oq6X3Kx7qY71XyVw==", + "resolved": "5.2.9", + "contentHash": "WhBAG/9hWiMHIXve4ZgwXP3spRwf7kFFfejf76QA5BvumgnPp8iDkDCiJugzAcpW1YaHB526z1UVxHhVT1E5qw==", "dependencies": { + "Microsoft.CSharp": "4.3.0", "NETStandard.Library": "1.6.1", + "System.ComponentModel.TypeConverter": "4.3.0", + "System.Dynamic.Runtime": "4.3.0", + "System.Net.Http": "4.3.4", + "System.Private.Uri": "4.3.2", + "System.Runtime.Serialization.Formatters": "4.3.0", "System.Runtime.Serialization.Json": "4.3.0", "System.Runtime.Serialization.Primitives": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Security.SecureString": "4.3.0", - "System.Xml.XDocument": "4.3.0" + "System.Xml.XDocument": "4.3.0", + "System.Xml.XmlDocument": "4.3.0" } }, "Microsoft.IdentityModel.JsonWebTokens": { @@ -785,8 +792,8 @@ }, "Microsoft.NETCore.Targets": { "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" + "resolved": "1.1.3", + "contentHash": "3Wrmi0kJDzClwAC+iBdUBpEKmEle8FQNsCs77fkiOIw/9oYA07bL1EZNX0kQ2OMN3xpwvl0vAtOCYY3ndDNlhQ==" }, "Microsoft.OData.Core": { "type": "Transitive", @@ -961,16 +968,6 @@ "Microsoft.NETCore.Targets": "1.1.0" } }, - "runtime.native.System.Data.SqlClient.sni": { - "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "AJfX7owAAkMjWQYhoml5IBfXh8UyYPjktn8pK0BFGAdKgBS7HqMz1fw5vdzfZUWfhtTPDGCjgNttt46ZyEmSjg==", - "dependencies": { - "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0", - "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0", - "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0" - } - }, "runtime.native.System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", @@ -1063,21 +1060,6 @@ "resolved": "4.3.2", "contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg==" }, - "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": { - "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg==" - }, - "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": { - "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ==" - }, - "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": { - "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA==" - }, "SendGrid": { "type": "Transitive", "resolved": "9.27.0", @@ -1319,29 +1301,69 @@ }, "System.Collections.NonGeneric": { "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "hMxFT2RhhlffyCdKLDXjx8WEC5JfCvNozAZxCablAuFRH74SCV4AgzE8yJCh/73bFnEoZgJ9MJmkjQ0dJmnKqA==", + "resolved": "4.3.0", + "contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", "dependencies": { - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" } }, "System.Collections.Specialized": { "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "/HKQyVP0yH1I0YtK7KJL/28snxHNH/bi+0lgk/+MbURF6ULhAE31MDI+NZDerNWu264YbxklXCCygISgm+HMug==", + "resolved": "4.3.0", + "contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==", "dependencies": { - "System.Collections.NonGeneric": "4.0.1", - "System.Globalization": "4.0.11", - "System.Globalization.Extensions": "4.0.1", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" + "System.Collections.NonGeneric": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Extensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.ComponentModel": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.ComponentModel.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==", + "dependencies": { + "System.ComponentModel": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.ComponentModel.TypeConverter": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Collections.NonGeneric": "4.3.0", + "System.Collections.Specialized": "4.3.0", + "System.ComponentModel": "4.3.0", + "System.ComponentModel.Primitives": "4.3.0", + "System.Globalization": "4.3.0", + "System.Linq": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Reflection.TypeExtensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" } }, "System.Configuration.ConfigurationManager": { @@ -1365,17 +1387,6 @@ "System.Text.Encoding": "4.3.0" } }, - "System.Data.SqlClient": { - "type": "Transitive", - "resolved": "4.6.0", - "contentHash": "gwItUWW1BMCckicFO85c8frFaMK8SGqYn5IeA3GSX4Lmid+CjXETfoHz7Uv+Vx6L0No7iRc/7cBL8gd6o9k9/g==", - "dependencies": { - "Microsoft.Win32.Registry": "4.5.0", - "System.Security.Principal.Windows": "4.5.0", - "System.Text.Encoding.CodePages": "4.5.0", - "runtime.native.System.Data.SqlClient.sni": "4.5.0" - } - }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.3.0", @@ -1479,24 +1490,23 @@ }, "System.Dynamic.Runtime": { "type": "Transitive", - "resolved": "4.0.11", - "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", + "resolved": "4.3.0", + "contentHash": "SNVi1E/vfWUAs/WYKhE9+qlS6KqK0YVhnlT0HQtr8pMIA8YX3lwy3uPMownDwdYISBdmAF/2holEIldVp85Wag==", "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.Linq": "4.1.0", - "System.Linq.Expressions": "4.1.0", - "System.ObjectModel": "4.0.12", - "System.Reflection": "4.1.0", - "System.Reflection.Emit": "4.0.1", - "System.Reflection.Emit.ILGeneration": "4.0.1", - "System.Reflection.Primitives": "4.0.1", - "System.Reflection.TypeExtensions": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Linq": "4.3.0", + "System.Linq.Expressions": "4.3.0", + "System.ObjectModel": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Emit": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Reflection.TypeExtensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" } }, "System.Formats.Asn1": { @@ -1959,6 +1969,15 @@ "System.Xml.XmlSerializer": "4.3.0" } }, + "System.Private.Uri": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "o1+7RJnu3Ik3PazR7Z7tJhjPdE000Eq2KGLLWhqJJKXj04wrS8lwb1OFtDF9jzXXADhUuZNJZlPc98uwwqmpFA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.1", + "Microsoft.NETCore.Targets": "1.1.3" + } + }, "System.Reflection": { "type": "Transitive", "resolved": "4.3.0", @@ -2126,6 +2145,18 @@ "System.Runtime.Extensions": "4.3.0" } }, + "System.Runtime.Serialization.Formatters": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KT591AkTNFOTbhZlaeMVvfax3RqhH1EJlcwF50Wm7sfnBLuHiOeZRRKrr1ns3NESkM20KPZ5Ol/ueMq5vg4QoQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Serialization.Primitives": "4.3.0" + } + }, "System.Runtime.Serialization.Json": { "type": "Transitive", "resolved": "4.3.0", diff --git a/util/Setup/packages.lock.json b/util/Setup/packages.lock.json index 2dd02b8b61..3b729e4899 100644 --- a/util/Setup/packages.lock.json +++ b/util/Setup/packages.lock.json @@ -156,21 +156,21 @@ }, "dbup-core": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "CR00QMAtHjfeMhwxFC5haoA0q4KZ5s6Y/AdZaT6oFjySik2eFEqVasuLgWSPKSiR7ti3z01BtiR7aD3nVckAsg==", + "resolved": "5.0.8", + "contentHash": "d+3RxJDftcarp1Y7jI78HRdRWRC7VFjM+rB2CFHWDmao6OixuLrqiyEo1DeuMNrWLTR5mmE8p1YTpFOvozI9ZQ==", "dependencies": { - "Microsoft.CSharp": "4.4.0", + "Microsoft.CSharp": "4.7.0", "System.Diagnostics.TraceSource": "4.3.0" } }, "dbup-sqlserver": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "/4hy4qmbWmtbLJGq8XCH3mtlgMld2G8rbXcjNDhqkq5y6dGZDW03OI4UsnQRxBiTQD5aYOcLuycK1dCJYhkdSw==", + "resolved": "5.0.8", + "contentHash": "b954l5Zgj9qgHtm16SLq2qGLJ0gIZtrWdh6JHoUsCLMHYW+0K2Oevabquw447At4U6X2t4CNuy7ZLHYf/Z/8yg==", "dependencies": { - "Microsoft.Azure.Services.AppAuthentication": "1.3.1", - "System.Data.SqlClient": "4.6.0", - "dbup-core": "4.5.0" + "Microsoft.Azure.Services.AppAuthentication": "1.6.2", + "Microsoft.Data.SqlClient": "5.0.1", + "dbup-core": "5.0.8" } }, "DnsClient": { @@ -436,10 +436,10 @@ }, "Microsoft.Azure.Services.AppAuthentication": { "type": "Transitive", - "resolved": "1.3.1", - "contentHash": "59CEcmUSlg5nYOzcyhdoUu+EQH4wrjCKj7dNuuPMeIjDCikAON9/KQXTQLfzfWTjDwqHIRptAAj0DTBp25lFcg==", + "resolved": "1.6.2", + "contentHash": "rSQhTv43ionr9rWvE4vxIe/i73XR5hoBYfh7UUgdaVOGW1MZeikR9RmgaJhonTylimCcCuJvrU0zXsSIFOsTGw==", "dependencies": { - "Microsoft.IdentityModel.Clients.ActiveDirectory": "4.3.0", + "Microsoft.IdentityModel.Clients.ActiveDirectory": "5.2.9", "System.Diagnostics.Process": "4.3.0" } }, @@ -729,15 +729,22 @@ }, "Microsoft.IdentityModel.Clients.ActiveDirectory": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "IRXnTCHxwnpnGBHVnTWd8RBJk7nsBsNZVl8j20kh234bP+oBILkt+6Iw5vQg5Q+sZmALt3Oq6X3Kx7qY71XyVw==", + "resolved": "5.2.9", + "contentHash": "WhBAG/9hWiMHIXve4ZgwXP3spRwf7kFFfejf76QA5BvumgnPp8iDkDCiJugzAcpW1YaHB526z1UVxHhVT1E5qw==", "dependencies": { + "Microsoft.CSharp": "4.3.0", "NETStandard.Library": "1.6.1", + "System.ComponentModel.TypeConverter": "4.3.0", + "System.Dynamic.Runtime": "4.3.0", + "System.Net.Http": "4.3.4", + "System.Private.Uri": "4.3.2", + "System.Runtime.Serialization.Formatters": "4.3.0", "System.Runtime.Serialization.Json": "4.3.0", "System.Runtime.Serialization.Primitives": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Security.SecureString": "4.3.0", - "System.Xml.XDocument": "4.3.0" + "System.Xml.XDocument": "4.3.0", + "System.Xml.XmlDocument": "4.3.0" } }, "Microsoft.IdentityModel.JsonWebTokens": { @@ -791,8 +798,8 @@ }, "Microsoft.NETCore.Targets": { "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" + "resolved": "1.1.3", + "contentHash": "3Wrmi0kJDzClwAC+iBdUBpEKmEle8FQNsCs77fkiOIw/9oYA07bL1EZNX0kQ2OMN3xpwvl0vAtOCYY3ndDNlhQ==" }, "Microsoft.OData.Core": { "type": "Transitive", @@ -967,16 +974,6 @@ "Microsoft.NETCore.Targets": "1.1.0" } }, - "runtime.native.System.Data.SqlClient.sni": { - "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "AJfX7owAAkMjWQYhoml5IBfXh8UyYPjktn8pK0BFGAdKgBS7HqMz1fw5vdzfZUWfhtTPDGCjgNttt46ZyEmSjg==", - "dependencies": { - "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0", - "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0", - "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0" - } - }, "runtime.native.System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", @@ -1069,21 +1066,6 @@ "resolved": "4.3.2", "contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg==" }, - "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": { - "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg==" - }, - "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": { - "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ==" - }, - "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": { - "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA==" - }, "SendGrid": { "type": "Transitive", "resolved": "9.27.0", @@ -1325,29 +1307,69 @@ }, "System.Collections.NonGeneric": { "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "hMxFT2RhhlffyCdKLDXjx8WEC5JfCvNozAZxCablAuFRH74SCV4AgzE8yJCh/73bFnEoZgJ9MJmkjQ0dJmnKqA==", + "resolved": "4.3.0", + "contentHash": "prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", "dependencies": { - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" } }, "System.Collections.Specialized": { "type": "Transitive", - "resolved": "4.0.1", - "contentHash": "/HKQyVP0yH1I0YtK7KJL/28snxHNH/bi+0lgk/+MbURF6ULhAE31MDI+NZDerNWu264YbxklXCCygISgm+HMug==", + "resolved": "4.3.0", + "contentHash": "Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==", "dependencies": { - "System.Collections.NonGeneric": "4.0.1", - "System.Globalization": "4.0.11", - "System.Globalization.Extensions": "4.0.1", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" + "System.Collections.NonGeneric": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Extensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.ComponentModel": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.ComponentModel.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==", + "dependencies": { + "System.ComponentModel": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.ComponentModel.TypeConverter": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "16pQ6P+EdhcXzPiEK4kbA953Fu0MNG2ovxTZU81/qsCd1zPRsKc3uif5NgvllCY598k6bI0KUyKW8fanlfaDQg==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Collections.NonGeneric": "4.3.0", + "System.Collections.Specialized": "4.3.0", + "System.ComponentModel": "4.3.0", + "System.ComponentModel.Primitives": "4.3.0", + "System.Globalization": "4.3.0", + "System.Linq": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Reflection.TypeExtensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" } }, "System.Configuration.ConfigurationManager": { @@ -1371,17 +1393,6 @@ "System.Text.Encoding": "4.3.0" } }, - "System.Data.SqlClient": { - "type": "Transitive", - "resolved": "4.6.0", - "contentHash": "gwItUWW1BMCckicFO85c8frFaMK8SGqYn5IeA3GSX4Lmid+CjXETfoHz7Uv+Vx6L0No7iRc/7cBL8gd6o9k9/g==", - "dependencies": { - "Microsoft.Win32.Registry": "4.5.0", - "System.Security.Principal.Windows": "4.5.0", - "System.Text.Encoding.CodePages": "4.5.0", - "runtime.native.System.Data.SqlClient.sni": "4.5.0" - } - }, "System.Diagnostics.Debug": { "type": "Transitive", "resolved": "4.3.0", @@ -1485,24 +1496,23 @@ }, "System.Dynamic.Runtime": { "type": "Transitive", - "resolved": "4.0.11", - "contentHash": "db34f6LHYM0U0JpE+sOmjar27BnqTVkbLJhgfwMpTdgTigG/Hna3m2MYVwnFzGGKnEJk2UXFuoVTr8WUbU91/A==", + "resolved": "4.3.0", + "contentHash": "SNVi1E/vfWUAs/WYKhE9+qlS6KqK0YVhnlT0HQtr8pMIA8YX3lwy3uPMownDwdYISBdmAF/2holEIldVp85Wag==", "dependencies": { - "System.Collections": "4.0.11", - "System.Diagnostics.Debug": "4.0.11", - "System.Globalization": "4.0.11", - "System.Linq": "4.1.0", - "System.Linq.Expressions": "4.1.0", - "System.ObjectModel": "4.0.12", - "System.Reflection": "4.1.0", - "System.Reflection.Emit": "4.0.1", - "System.Reflection.Emit.ILGeneration": "4.0.1", - "System.Reflection.Primitives": "4.0.1", - "System.Reflection.TypeExtensions": "4.1.0", - "System.Resources.ResourceManager": "4.0.1", - "System.Runtime": "4.1.0", - "System.Runtime.Extensions": "4.1.0", - "System.Threading": "4.0.11" + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Linq": "4.3.0", + "System.Linq.Expressions": "4.3.0", + "System.ObjectModel": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Emit": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Reflection.TypeExtensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" } }, "System.Formats.Asn1": { @@ -1965,6 +1975,15 @@ "System.Xml.XmlSerializer": "4.3.0" } }, + "System.Private.Uri": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "o1+7RJnu3Ik3PazR7Z7tJhjPdE000Eq2KGLLWhqJJKXj04wrS8lwb1OFtDF9jzXXADhUuZNJZlPc98uwwqmpFA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.1", + "Microsoft.NETCore.Targets": "1.1.3" + } + }, "System.Reflection": { "type": "Transitive", "resolved": "4.3.0", @@ -2132,6 +2151,18 @@ "System.Runtime.Extensions": "4.3.0" } }, + "System.Runtime.Serialization.Formatters": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KT591AkTNFOTbhZlaeMVvfax3RqhH1EJlcwF50Wm7sfnBLuHiOeZRRKrr1ns3NESkM20KPZ5Ol/ueMq5vg4QoQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Serialization.Primitives": "4.3.0" + } + }, "System.Runtime.Serialization.Json": { "type": "Transitive", "resolved": "4.3.0", @@ -2659,9 +2690,9 @@ "migrator": { "type": "Project", "dependencies": { - "Core": "[2023.1.0, )", + "Core": "[2023.2.0, )", "Microsoft.Extensions.Logging": "[6.0.0, )", - "dbup-sqlserver": "[4.5.0, )" + "dbup-sqlserver": "[5.0.8, )" } } } From 4fb2649faf2e4c75813f3848a27840ed1302d187 Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Wed, 22 Feb 2023 14:48:45 -0500 Subject: [PATCH 41/49] Fix typo (#2731) --- .github/workflows/cleanup-after-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cleanup-after-pr.yml b/.github/workflows/cleanup-after-pr.yml index c1eca91079..ab6920aca2 100644 --- a/.github/workflows/cleanup-after-pr.yml +++ b/.github/workflows/cleanup-after-pr.yml @@ -21,7 +21,7 @@ jobs: - name: Login to Azure ACR run: az acr login -n bitwardenqa - k + - name: Login to Azure - PROD Subscription uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a with: From a430518cdb76b2f9690378f9608c116ac696d7f2 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Wed, 22 Feb 2023 19:11:16 -0500 Subject: [PATCH 42/49] [PM-1128] Add Migration to Sync OrganizationUserUserDetailsView (#2732) * Updated OrganizationUserUserDetailsView to include AccessSecretsManager which was missing in the migration hence making the view script out of sync with the migrations * Formatted file * Formatted file --- ...ganizationUserUserDetailsViewOutOfSync.sql | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 util/Migrator/DbScripts/2023-02-22_FixOrganizationUserUserDetailsViewOutOfSync.sql diff --git a/util/Migrator/DbScripts/2023-02-22_FixOrganizationUserUserDetailsViewOutOfSync.sql b/util/Migrator/DbScripts/2023-02-22_FixOrganizationUserUserDetailsViewOutOfSync.sql new file mode 100644 index 0000000000..6e0ed991c4 --- /dev/null +++ b/util/Migrator/DbScripts/2023-02-22_FixOrganizationUserUserDetailsViewOutOfSync.sql @@ -0,0 +1,45 @@ +CREATE OR ALTER VIEW [dbo].[OrganizationUserUserDetailsView] +AS +SELECT + OU.[Id], + OU.[UserId], + OU.[OrganizationId], + U.[Name], + ISNULL(U.[Email], OU.[Email]) Email, + U.[AvatarColor], + U.[TwoFactorProviders], + U.[Premium], + OU.[Status], + OU.[Type], + OU.[AccessAll], + OU.[AccessSecretsManager], + OU.[ExternalId], + SU.[ExternalId] SsoExternalId, + OU.[Permissions], + OU.[ResetPasswordKey], + U.[UsesKeyConnector] +FROM + [dbo].[OrganizationUser] OU +LEFT JOIN + [dbo].[User] U ON U.[Id] = OU.[UserId] +LEFT JOIN + [dbo].[SsoUser] SU ON SU.[UserId] = OU.[UserId] AND SU.[OrganizationId] = OU.[OrganizationId] +GO + +IF OBJECT_ID('[dbo].[OrganizationUserUserDetails_ReadById]') IS NOT NULL +BEGIN + EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationUserUserDetails_ReadById]'; +END +GO + +IF OBJECT_ID('[dbo].[OrganizationUserUserDetails_ReadByOrganizationId]') IS NOT NULL +BEGIN + EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationUserUserDetails_ReadByOrganizationId]'; +END +GO + +IF OBJECT_ID('[dbo].[OrganizationUser_ReadByMinimumRole]') IS NOT NULL +BEGIN + EXECUTE sp_refreshsqlmodule N'[dbo].[OrganizationUser_ReadByMinimumRole]'; +END +GO From 1c66365e9639caafae553cd13bff3eca5029c759 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Wed, 22 Feb 2023 19:25:26 -0500 Subject: [PATCH 43/49] Made correction to the domain used to domains that have been unverified after 72 hours. Instead of doing a greater than or equal to the condition is set to a fixed period 4, so domains after 4 days which are uneverified would not be picked up by the service (#2729) --- .../Repositories/OrganizationDomainRepository.cs | 2 +- .../OrganizationDomain_ReadIfExpired.sql | 5 +++-- ...ReturningExpiredDomainsAfterSpecifiedPeriod.sql | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 util/Migrator/DbScripts/2023-02-22_FixReturningExpiredDomainsAfterSpecifiedPeriod.sql diff --git a/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs b/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs index 1f3cd375e5..8b453d5e5e 100644 --- a/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs @@ -119,7 +119,7 @@ public class OrganizationDomainRepository : Repository (DateTime.UtcNow - x.CreationDate).Days >= 4 + .Where(x => (DateTime.UtcNow - x.CreationDate).Days == 4 && x.VerifiedDate == null) .ToList(); diff --git a/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadIfExpired.sql b/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadIfExpired.sql index d548c97b99..b2a1a9d3fb 100644 --- a/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadIfExpired.sql +++ b/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadIfExpired.sql @@ -8,6 +8,7 @@ BEGIN FROM [dbo].[OrganizationDomain] WHERE - DATEDIFF(DAY, [CreationDate], GETUTCDATE()) >= 4 --Get domains that have not been verified after 3 days (72 hours) + DATEDIFF(DAY, [CreationDate], GETUTCDATE()) = 4 --Get domains that have not been verified after 3 days (72 hours) AND - [VerifiedDate] IS NULL \ No newline at end of file + [VerifiedDate] IS NULL +END \ No newline at end of file diff --git a/util/Migrator/DbScripts/2023-02-22_FixReturningExpiredDomainsAfterSpecifiedPeriod.sql b/util/Migrator/DbScripts/2023-02-22_FixReturningExpiredDomainsAfterSpecifiedPeriod.sql new file mode 100644 index 0000000000..67fef99b84 --- /dev/null +++ b/util/Migrator/DbScripts/2023-02-22_FixReturningExpiredDomainsAfterSpecifiedPeriod.sql @@ -0,0 +1,14 @@ +CREATE OR ALTER PROCEDURE [dbo].[OrganizationDomain_ReadIfExpired] +AS +BEGIN + SET NOCOUNT OFF + + SELECT + * + FROM + [dbo].[OrganizationDomain] + WHERE + DATEDIFF(DAY, [CreationDate], GETUTCDATE()) = 4 --Get domains that have not been verified after 3 days (72 hours) + AND + [VerifiedDate] IS NULL +END \ No newline at end of file From 992e3a74d64d490520d3154c8ae2a10a7dfec5cf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 23 Feb 2023 09:50:36 -0500 Subject: [PATCH 44/49] Bumped version to 2023.2.1 (#2736) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 822a4f48d2..f4decd764e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ net6.0 - 2023.2.0 + 2023.2.1 Bit.$(MSBuildProjectName) true enable From 7d0bba3a29bf1ae562d5f1ca479ddfb05ea247c9 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Thu, 23 Feb 2023 12:01:01 -0500 Subject: [PATCH 45/49] Improve EF Migration Workflow by Starting Required Docker Services Automatically (#2725) * Added check to detremine if certain docker services are running and if not starts them * used to docker no recreate flag to simplify the process --- dev/ef_migrate.ps1 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dev/ef_migrate.ps1 b/dev/ef_migrate.ps1 index 97e9939344..14cf13088b 100644 --- a/dev/ef_migrate.ps1 +++ b/dev/ef_migrate.ps1 @@ -4,6 +4,13 @@ param ( $Name ) +# DB service provider name +$service = "mysql" + +Write-Output "--- Attempting to start $service service ---" + +docker-compose --profile $service up -d --no-recreate + dotnet tool restore $providers = @{ From 4643f5960ec304ccfc300b0ce97cd199a8d6b5c4 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Fri, 24 Feb 2023 07:54:19 +1000 Subject: [PATCH 46/49] [EC-635] Extract organizationService.UpdateLicenseAsync to a command (#2408) * move UpdateLicenseAsync from service to command * create new SelfHostedOrganizationDetails view model and move license validation logic there * move occupied seat count logic to database level --- .../src/Sso/Controllers/AccountController.cs | 2 +- src/Admin/Models/OrganizationViewModel.cs | 2 +- .../Controllers/OrganizationsController.cs | 13 +- ...elfHostedOrganizationLicensesController.cs | 28 +- src/Core/Entities/Organization.cs | 30 ++ .../Models/Business/OrganizationLicense.cs | 31 +- .../OrganizationUserUserDetails.cs | 8 - .../SelfHostedOrganizationDetails.cs | 145 +++++++ .../Cloud/CloudGetOrganizationLicenseQuery.cs | 4 +- .../IUpdateOrganizationLicenseCommand.cs | 13 + .../UpdateOrganizationLicenseCommand.cs | 66 +++ ...OrganizationServiceCollectionExtensions.cs | 5 +- .../Repositories/IOrganizationRepository.cs | 2 + .../IOrganizationUserRepository.cs | 1 + src/Core/Services/IOrganizationService.cs | 3 +- .../Implementations/OrganizationService.cs | 226 +---------- .../Repositories/OrganizationRepository.cs | 40 ++ .../OrganizationUserRepository.cs | 13 + .../Models/Organization.cs | 1 + .../Repositories/OrganizationRepository.cs | 39 +- .../OrganizationUserRepository.cs | 6 + ...dOccupiedSeatCountByOrganizationIdQuery.cs | 22 + src/Sql/Sql.sqlproj | 5 +- .../Group_ReadCountByOrganizationId.sql | 13 + ..._ReadOccupiedSeatCountByOrganizationId.sql | 14 + .../Organization_ReadByLicenseKey.sql | 13 + ...Organization_ReadSelfHostedDetailsById.sql | 15 + .../OrganizationsControllerTests.cs | 5 +- .../SelfHostedOrganizationDetailsTests.cs | 379 ++++++++++++++++++ ...02-16_00_SelfHostedOrganizationDetails.sql | 62 +++ 30 files changed, 967 insertions(+), 239 deletions(-) create mode 100644 src/Core/Models/Data/Organizations/SelfHostedOrganizationDetails.cs create mode 100644 src/Core/OrganizationFeatures/OrganizationLicenses/Interfaces/IUpdateOrganizationLicenseCommand.cs create mode 100644 src/Core/OrganizationFeatures/OrganizationLicenses/UpdateOrganizationLicenseCommand.cs create mode 100644 src/Infrastructure.EntityFramework/Repositories/Queries/OrganizationUserReadOccupiedSeatCountByOrganizationIdQuery.cs create mode 100644 src/Sql/dbo/Stored Procedures/Group_ReadCountByOrganizationId.sql create mode 100644 src/Sql/dbo/Stored Procedures/OrganizationUser_ReadOccupiedSeatCountByOrganizationId.sql create mode 100644 src/Sql/dbo/Stored Procedures/Organization_ReadByLicenseKey.sql create mode 100644 src/Sql/dbo/Stored Procedures/Organization_ReadSelfHostedDetailsById.sql create mode 100644 test/Core.Test/Models/Data/SelfHostedOrganizationDetailsTests.cs create mode 100644 util/Migrator/DbScripts/2023-02-16_00_SelfHostedOrganizationDetails.sql diff --git a/bitwarden_license/src/Sso/Controllers/AccountController.cs b/bitwarden_license/src/Sso/Controllers/AccountController.cs index fdbcf0f0d1..2f8e8e90f7 100644 --- a/bitwarden_license/src/Sso/Controllers/AccountController.cs +++ b/bitwarden_license/src/Sso/Controllers/AccountController.cs @@ -483,7 +483,7 @@ public class AccountController : Controller // Before any user creation - if Org User doesn't exist at this point - make sure there are enough seats to add one if (orgUser == null && organization.Seats.HasValue) { - var occupiedSeats = await _organizationService.GetOccupiedSeatCount(organization); + var occupiedSeats = await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); var initialSeatCount = organization.Seats.Value; var availableSeats = initialSeatCount - occupiedSeats; var prorationDate = DateTime.UtcNow; diff --git a/src/Admin/Models/OrganizationViewModel.cs b/src/Admin/Models/OrganizationViewModel.cs index 31504f9371..6799f34e26 100644 --- a/src/Admin/Models/OrganizationViewModel.cs +++ b/src/Admin/Models/OrganizationViewModel.cs @@ -18,7 +18,7 @@ public class OrganizationViewModel UserInvitedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Invited); UserAcceptedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Accepted); UserConfirmedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Confirmed); - OccupiedSeatCount = orgUsers.Count(u => u.OccupiesOrganizationSeat); + OccupiedSeatCount = UserInvitedCount + UserAcceptedCount + UserConfirmedCount; CipherCount = ciphers.Count(); CollectionCount = collections.Count(); GroupCount = groups?.Count() ?? 0; diff --git a/src/Api/Controllers/OrganizationsController.cs b/src/Api/Controllers/OrganizationsController.cs index 57c39359ba..e4c4003199 100644 --- a/src/Api/Controllers/OrganizationsController.cs +++ b/src/Api/Controllers/OrganizationsController.cs @@ -39,6 +39,7 @@ public class OrganizationsController : Controller private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand; private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand; private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository; + private readonly IUpdateOrganizationLicenseCommand _updateOrganizationLicenseCommand; private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery; private readonly GlobalSettings _globalSettings; @@ -56,6 +57,7 @@ public class OrganizationsController : Controller IRotateOrganizationApiKeyCommand rotateOrganizationApiKeyCommand, ICreateOrganizationApiKeyCommand createOrganizationApiKeyCommand, IOrganizationApiKeyRepository organizationApiKeyRepository, + IUpdateOrganizationLicenseCommand updateOrganizationLicenseCommand, ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery, GlobalSettings globalSettings) { @@ -72,6 +74,7 @@ public class OrganizationsController : Controller _rotateOrganizationApiKeyCommand = rotateOrganizationApiKeyCommand; _createOrganizationApiKeyCommand = createOrganizationApiKeyCommand; _organizationApiKeyRepository = organizationApiKeyRepository; + _updateOrganizationLicenseCommand = updateOrganizationLicenseCommand; _cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery; _globalSettings = globalSettings; } @@ -461,7 +464,15 @@ public class OrganizationsController : Controller throw new BadRequestException("Invalid license"); } - await _organizationService.UpdateLicenseAsync(new Guid(id), license); + var selfHostedOrganizationDetails = await _organizationRepository.GetSelfHostedOrganizationDetailsById(orgIdGuid); + if (selfHostedOrganizationDetails == null) + { + throw new NotFoundException(); + } + + var existingOrganization = await _organizationRepository.GetByLicenseKeyAsync(license.LicenseKey); + + await _updateOrganizationLicenseCommand.UpdateLicenseAsync(selfHostedOrganizationDetails, license, existingOrganization); } [HttpPost("{id}/import")] diff --git a/src/Api/Controllers/SelfHosted/SelfHostedOrganizationLicensesController.cs b/src/Api/Controllers/SelfHosted/SelfHostedOrganizationLicensesController.cs index 92cf50ae41..c821204c75 100644 --- a/src/Api/Controllers/SelfHosted/SelfHostedOrganizationLicensesController.cs +++ b/src/Api/Controllers/SelfHosted/SelfHostedOrganizationLicensesController.cs @@ -27,6 +27,7 @@ public class SelfHostedOrganizationLicensesController : Controller private readonly IOrganizationService _organizationService; private readonly IOrganizationRepository _organizationRepository; private readonly IUserService _userService; + private readonly IUpdateOrganizationLicenseCommand _updateOrganizationLicenseCommand; public SelfHostedOrganizationLicensesController( ICurrentContext currentContext, @@ -34,7 +35,8 @@ public class SelfHostedOrganizationLicensesController : Controller IOrganizationConnectionRepository organizationConnectionRepository, IOrganizationService organizationService, IOrganizationRepository organizationRepository, - IUserService userService) + IUserService userService, + IUpdateOrganizationLicenseCommand updateOrganizationLicenseCommand) { _currentContext = currentContext; _selfHostedGetOrganizationLicenseQuery = selfHostedGetOrganizationLicenseQuery; @@ -42,6 +44,7 @@ public class SelfHostedOrganizationLicensesController : Controller _organizationService = organizationService; _organizationRepository = organizationRepository; _userService = userService; + _updateOrganizationLicenseCommand = updateOrganizationLicenseCommand; } [HttpPost("")] @@ -79,25 +82,33 @@ public class SelfHostedOrganizationLicensesController : Controller throw new BadRequestException("Invalid license"); } - await _organizationService.UpdateLicenseAsync(new Guid(id), license); + var selfHostedOrganizationDetails = await _organizationRepository.GetSelfHostedOrganizationDetailsById(orgIdGuid); + if (selfHostedOrganizationDetails == null) + { + throw new NotFoundException(); + } + + var currentOrganization = await _organizationRepository.GetByLicenseKeyAsync(license.LicenseKey); + + await _updateOrganizationLicenseCommand.UpdateLicenseAsync(selfHostedOrganizationDetails, license, currentOrganization); } [HttpPost("{id}/sync")] public async Task SyncLicenseAsync(string id) { - var organization = await _organizationRepository.GetByIdAsync(new Guid(id)); - if (organization == null) + var selfHostedOrganizationDetails = await _organizationRepository.GetSelfHostedOrganizationDetailsById(new Guid(id)); + if (selfHostedOrganizationDetails == null) { throw new NotFoundException(); } - if (!await _currentContext.OrganizationOwner(organization.Id)) + if (!await _currentContext.OrganizationOwner(selfHostedOrganizationDetails.Id)) { throw new NotFoundException(); } var billingSyncConnection = - (await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organization.Id, + (await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(selfHostedOrganizationDetails.Id, OrganizationConnectionType.CloudBillingSync)).FirstOrDefault(); if (billingSyncConnection == null) { @@ -105,9 +116,10 @@ public class SelfHostedOrganizationLicensesController : Controller } var license = - await _selfHostedGetOrganizationLicenseQuery.GetLicenseAsync(organization, billingSyncConnection); + await _selfHostedGetOrganizationLicenseQuery.GetLicenseAsync(selfHostedOrganizationDetails, billingSyncConnection); + var currentOrganization = await _organizationRepository.GetByLicenseKeyAsync(license.LicenseKey); - await _organizationService.UpdateLicenseAsync(organization.Id, license); + await _updateOrganizationLicenseCommand.UpdateLicenseAsync(selfHostedOrganizationDetails, license, currentOrganization); var config = billingSyncConnection.GetConfig(); config.LastLicenseSync = DateTime.Now; diff --git a/src/Core/Entities/Organization.cs b/src/Core/Entities/Organization.cs index cbb2fb64e7..0f6d75ae0e 100644 --- a/src/Core/Entities/Organization.cs +++ b/src/Core/Entities/Organization.cs @@ -2,6 +2,7 @@ using System.Text.Json; using Bit.Core.Enums; using Bit.Core.Models; +using Bit.Core.Models.Business; using Bit.Core.Utilities; namespace Bit.Core.Entities; @@ -197,4 +198,33 @@ public class Organization : ITableObject, ISubscriber, IStorable, IStorabl return providers[provider]; } + + public void UpdateFromLicense(OrganizationLicense license) + { + Name = license.Name; + BusinessName = license.BusinessName; + BillingEmail = license.BillingEmail; + PlanType = license.PlanType; + Seats = license.Seats; + MaxCollections = license.MaxCollections; + UseGroups = license.UseGroups; + UseDirectory = license.UseDirectory; + UseEvents = license.UseEvents; + UseTotp = license.UseTotp; + Use2fa = license.Use2fa; + UseApi = license.UseApi; + UsePolicies = license.UsePolicies; + UseSso = license.UseSso; + UseKeyConnector = license.UseKeyConnector; + UseScim = license.UseScim; + UseResetPassword = license.UseResetPassword; + SelfHost = license.SelfHost; + UsersGetPremium = license.UsersGetPremium; + UseCustomPermissions = license.UseCustomPermissions; + Plan = license.Plan; + Enabled = license.Enabled; + ExpirationDate = license.Expires; + LicenseKey = license.LicenseKey; + RevisionDate = DateTime.UtcNow; + } } diff --git a/src/Core/Models/Business/OrganizationLicense.cs b/src/Core/Models/Business/OrganizationLicense.cs index 03cba08c49..58cce4cffb 100644 --- a/src/Core/Models/Business/OrganizationLicense.cs +++ b/src/Core/Models/Business/OrganizationLicense.cs @@ -199,21 +199,42 @@ public class OrganizationLicense : ILicense } } - public bool CanUse(IGlobalSettings globalSettings) + public bool CanUse(IGlobalSettings globalSettings, ILicensingService licensingService, out string exception) { if (!Enabled || Issued > DateTime.UtcNow || Expires < DateTime.UtcNow) { + exception = "Invalid license. Your organization is disabled or the license has expired."; return false; } - if (ValidLicenseVersion) + if (!ValidLicenseVersion) { - return InstallationId == globalSettings.Installation.Id && SelfHost; + exception = $"Version {Version} is not supported."; + return false; } - else + + if (InstallationId != globalSettings.Installation.Id || !SelfHost) { - throw new NotSupportedException($"Version {Version} is not supported."); + exception = "Invalid license. Make sure your license allows for on-premise " + + "hosting of organizations and that the installation id matches your current installation."; + return false; } + + if (LicenseType != null && LicenseType != Enums.LicenseType.Organization) + { + exception = "Premium licenses cannot be applied to an organization. " + + "Upload this license from your personal account settings page."; + return false; + } + + if (!licensingService.VerifyLicense(this)) + { + exception = "Invalid license."; + return false; + } + + exception = ""; + return true; } public bool VerifyData(Organization organization, IGlobalSettings globalSettings) diff --git a/src/Core/Models/Data/Organizations/OrganizationUsers/OrganizationUserUserDetails.cs b/src/Core/Models/Data/Organizations/OrganizationUsers/OrganizationUserUserDetails.cs index 3bb9416813..98bad1ccb6 100644 --- a/src/Core/Models/Data/Organizations/OrganizationUsers/OrganizationUserUserDetails.cs +++ b/src/Core/Models/Data/Organizations/OrganizationUsers/OrganizationUserUserDetails.cs @@ -62,14 +62,6 @@ public class OrganizationUserUserDetails : IExternal, ITwoFactorProvidersUser return Premium.GetValueOrDefault(false); } - public bool OccupiesOrganizationSeat - { - get - { - return Status != OrganizationUserStatusType.Revoked; - } - } - public Permissions GetPermissions() { return string.IsNullOrWhiteSpace(Permissions) ? null diff --git a/src/Core/Models/Data/Organizations/SelfHostedOrganizationDetails.cs b/src/Core/Models/Data/Organizations/SelfHostedOrganizationDetails.cs new file mode 100644 index 0000000000..a2c0811762 --- /dev/null +++ b/src/Core/Models/Data/Organizations/SelfHostedOrganizationDetails.cs @@ -0,0 +1,145 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Models.Business; +using Bit.Core.Models.OrganizationConnectionConfigs; + +namespace Bit.Core.Models.Data.Organizations; + +public class SelfHostedOrganizationDetails : Organization +{ + public int OccupiedSeatCount { get; set; } + public int CollectionCount { get; set; } + public int GroupCount { get; set; } + public IEnumerable OrganizationUsers { get; set; } + public IEnumerable Policies { get; set; } + public SsoConfig SsoConfig { get; set; } + public IEnumerable ScimConnections { get; set; } + + public bool CanUseLicense(OrganizationLicense license, out string exception) + { + if (license.Seats.HasValue && OccupiedSeatCount > license.Seats.Value) + { + exception = $"Your organization currently has {OccupiedSeatCount} seats filled. " + + $"Your new license only has ({license.Seats.Value}) seats. Remove some users."; + return false; + } + + if (license.MaxCollections.HasValue && CollectionCount > license.MaxCollections.Value) + { + exception = $"Your organization currently has {CollectionCount} collections. " + + $"Your new license allows for a maximum of ({license.MaxCollections.Value}) collections. " + + "Remove some collections."; + return false; + } + + if (!license.UseGroups && UseGroups && GroupCount > 1) + { + exception = $"Your organization currently has {GroupCount} groups. " + + $"Your new license does not allow for the use of groups. Remove all groups."; + return false; + } + + var enabledPolicyCount = Policies.Count(p => p.Enabled); + if (!license.UsePolicies && UsePolicies && enabledPolicyCount > 0) + { + exception = $"Your organization currently has {enabledPolicyCount} enabled " + + $"policies. Your new license does not allow for the use of policies. Disable all policies."; + return false; + } + + if (!license.UseSso && UseSso && SsoConfig is { Enabled: true }) + { + exception = $"Your organization currently has a SSO configuration. " + + $"Your new license does not allow for the use of SSO. Disable your SSO configuration."; + return false; + } + + if (!license.UseKeyConnector && UseKeyConnector && SsoConfig?.Data != null && + SsoConfig.GetData().KeyConnectorEnabled) + { + exception = $"Your organization currently has Key Connector enabled. " + + $"Your new license does not allow for the use of Key Connector. Disable your Key Connector."; + return false; + } + + if (!license.UseScim && UseScim && ScimConnections != null && + ScimConnections.Any(c => c.GetConfig() is { Enabled: true })) + { + exception = "Your new plan does not allow the SCIM feature. " + + "Disable your SCIM configuration."; + return false; + } + + if (!license.UseCustomPermissions && UseCustomPermissions && + OrganizationUsers.Any(ou => ou.Type == OrganizationUserType.Custom)) + { + exception = "Your new plan does not allow the Custom Permissions feature. " + + "Disable your Custom Permissions configuration."; + return false; + } + + if (!license.UseResetPassword && UseResetPassword && + Policies.Any(p => p.Type == PolicyType.ResetPassword && p.Enabled)) + { + exception = "Your new license does not allow the Password Reset feature. " + + "Disable your Password Reset policy."; + return false; + } + + exception = ""; + return true; + } + + public Organization ToOrganization() + { + // Any new Organization properties must be added here for them to flow through to self-hosted organizations + return new Organization + { + Id = Id, + Identifier = Identifier, + Name = Name, + BusinessName = BusinessName, + BusinessAddress1 = BusinessAddress1, + BusinessAddress2 = BusinessAddress2, + BusinessAddress3 = BusinessAddress3, + BusinessCountry = BusinessCountry, + BusinessTaxNumber = BusinessTaxNumber, + BillingEmail = BillingEmail, + Plan = Plan, + PlanType = PlanType, + Seats = Seats, + MaxCollections = MaxCollections, + UsePolicies = UsePolicies, + UseSso = UseSso, + UseKeyConnector = UseKeyConnector, + UseScim = UseScim, + UseGroups = UseGroups, + UseDirectory = UseDirectory, + UseEvents = UseEvents, + UseTotp = UseTotp, + Use2fa = Use2fa, + UseApi = UseApi, + UseResetPassword = UseResetPassword, + UseSecretsManager = UseSecretsManager, + SelfHost = SelfHost, + UsersGetPremium = UsersGetPremium, + UseCustomPermissions = UseCustomPermissions, + Storage = Storage, + MaxStorageGb = MaxStorageGb, + Gateway = Gateway, + GatewayCustomerId = GatewayCustomerId, + GatewaySubscriptionId = GatewaySubscriptionId, + ReferenceData = ReferenceData, + Enabled = Enabled, + LicenseKey = LicenseKey, + PublicKey = PublicKey, + PrivateKey = PrivateKey, + TwoFactorProviders = TwoFactorProviders, + ExpirationDate = ExpirationDate, + CreationDate = CreationDate, + RevisionDate = RevisionDate, + MaxAutoscaleSeats = MaxAutoscaleSeats, + OwnersNotifiedOfAutoscaling = OwnersNotifiedOfAutoscaling, + }; + } +} diff --git a/src/Core/OrganizationFeatures/OrganizationLicenses/Cloud/CloudGetOrganizationLicenseQuery.cs b/src/Core/OrganizationFeatures/OrganizationLicenses/Cloud/CloudGetOrganizationLicenseQuery.cs index ff8a6d34fb..a8d3a1638f 100644 --- a/src/Core/OrganizationFeatures/OrganizationLicenses/Cloud/CloudGetOrganizationLicenseQuery.cs +++ b/src/Core/OrganizationFeatures/OrganizationLicenses/Cloud/CloudGetOrganizationLicenseQuery.cs @@ -32,7 +32,7 @@ public class CloudGetOrganizationLicenseQuery : ICloudGetOrganizationLicenseQuer throw new BadRequestException("Invalid installation id"); } - var subInfo = await _paymentService.GetSubscriptionAsync(organization); - return new OrganizationLicense(organization, subInfo, installationId, _licensingService, version); + var subscriptionInfo = await _paymentService.GetSubscriptionAsync(organization); + return new OrganizationLicense(organization, subscriptionInfo, installationId, _licensingService, version); } } diff --git a/src/Core/OrganizationFeatures/OrganizationLicenses/Interfaces/IUpdateOrganizationLicenseCommand.cs b/src/Core/OrganizationFeatures/OrganizationLicenses/Interfaces/IUpdateOrganizationLicenseCommand.cs new file mode 100644 index 0000000000..2ba82c1c62 --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationLicenses/Interfaces/IUpdateOrganizationLicenseCommand.cs @@ -0,0 +1,13 @@ +#nullable enable + +using Bit.Core.Entities; +using Bit.Core.Models.Business; +using Bit.Core.Models.Data.Organizations; + +namespace Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces; + +public interface IUpdateOrganizationLicenseCommand +{ + Task UpdateLicenseAsync(SelfHostedOrganizationDetails selfHostedOrganization, + OrganizationLicense license, Organization? currentOrganizationUsingLicenseKey); +} diff --git a/src/Core/OrganizationFeatures/OrganizationLicenses/UpdateOrganizationLicenseCommand.cs b/src/Core/OrganizationFeatures/OrganizationLicenses/UpdateOrganizationLicenseCommand.cs new file mode 100644 index 0000000000..583f521434 --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationLicenses/UpdateOrganizationLicenseCommand.cs @@ -0,0 +1,66 @@ +#nullable enable + +using System.Text.Json; +using Bit.Core.Entities; +using Bit.Core.Exceptions; +using Bit.Core.Models.Business; +using Bit.Core.Models.Data.Organizations; +using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces; +using Bit.Core.Services; +using Bit.Core.Settings; +using Bit.Core.Utilities; + +namespace Bit.Core.OrganizationFeatures.OrganizationLicenses; + +public class UpdateOrganizationLicenseCommand : IUpdateOrganizationLicenseCommand +{ + private readonly ILicensingService _licensingService; + private readonly IGlobalSettings _globalSettings; + private readonly IOrganizationService _organizationService; + + public UpdateOrganizationLicenseCommand( + ILicensingService licensingService, + IGlobalSettings globalSettings, + IOrganizationService organizationService) + { + _licensingService = licensingService; + _globalSettings = globalSettings; + _organizationService = organizationService; + } + + public async Task UpdateLicenseAsync(SelfHostedOrganizationDetails selfHostedOrganization, + OrganizationLicense license, Organization? currentOrganizationUsingLicenseKey) + { + if (currentOrganizationUsingLicenseKey != null && currentOrganizationUsingLicenseKey.Id != selfHostedOrganization.Id) + { + throw new BadRequestException("License is already in use by another organization."); + } + + var canUse = license.CanUse(_globalSettings, _licensingService, out var exception) && + selfHostedOrganization.CanUseLicense(license, out exception); + + if (!canUse) + { + throw new BadRequestException(exception); + } + + await WriteLicenseFileAsync(selfHostedOrganization, license); + await UpdateOrganizationAsync(selfHostedOrganization, license); + } + + private async Task WriteLicenseFileAsync(Organization organization, OrganizationLicense license) + { + var dir = $"{_globalSettings.LicenseDirectory}/organization"; + Directory.CreateDirectory(dir); + await using var fs = new FileStream(Path.Combine(dir, $"{organization.Id}.json"), FileMode.Create); + await JsonSerializer.SerializeAsync(fs, license, JsonHelpers.Indented); + } + + private async Task UpdateOrganizationAsync(SelfHostedOrganizationDetails selfHostedOrganizationDetails, OrganizationLicense license) + { + var organization = selfHostedOrganizationDetails.ToOrganization(); + organization.UpdateFromLicense(license); + + await _organizationService.ReplaceAndUpdateCacheAsync(organization); + } +} diff --git a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs index 0eaf160452..983fa3b352 100644 --- a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs +++ b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs @@ -36,7 +36,7 @@ public static class OrganizationServiceCollectionExtensions services.AddOrganizationApiKeyCommandsQueries(); services.AddOrganizationCollectionCommands(); services.AddOrganizationGroupCommands(); - services.AddOrganizationLicenseCommandQueries(); + services.AddOrganizationLicenseCommandsQueries(); services.AddOrganizationDomainCommandsQueries(); } @@ -91,10 +91,11 @@ public static class OrganizationServiceCollectionExtensions services.AddScoped(); } - private static void AddOrganizationLicenseCommandQueries(this IServiceCollection services) + private static void AddOrganizationLicenseCommandsQueries(this IServiceCollection services) { services.AddScoped(); services.AddScoped(); + services.AddScoped(); } private static void AddOrganizationDomainCommandsQueries(this IServiceCollection services) diff --git a/src/Core/Repositories/IOrganizationRepository.cs b/src/Core/Repositories/IOrganizationRepository.cs index 690bff9139..e8bdc869ac 100644 --- a/src/Core/Repositories/IOrganizationRepository.cs +++ b/src/Core/Repositories/IOrganizationRepository.cs @@ -11,4 +11,6 @@ public interface IOrganizationRepository : IRepository Task> SearchAsync(string name, string userEmail, bool? paid, int skip, int take); Task UpdateStorageAsync(Guid id); Task> GetManyAbilitiesAsync(); + Task GetByLicenseKeyAsync(string licenseKey); + Task GetSelfHostedOrganizationDetailsById(Guid id); } diff --git a/src/Core/Repositories/IOrganizationUserRepository.cs b/src/Core/Repositories/IOrganizationUserRepository.cs index 3844b1a87d..207295b8b1 100644 --- a/src/Core/Repositories/IOrganizationUserRepository.cs +++ b/src/Core/Repositories/IOrganizationUserRepository.cs @@ -13,6 +13,7 @@ public interface IOrganizationUserRepository : IRepository> GetManyByUserAsync(Guid userId); Task> GetManyByOrganizationAsync(Guid organizationId, OrganizationUserType? type); Task GetCountByOrganizationAsync(Guid organizationId, string email, bool onlyRegisteredUsers); + Task GetOccupiedSeatCountByOrganizationIdAsync(Guid organizationId); Task> SelectKnownEmailsAsync(Guid organizationId, IEnumerable emails, bool onlyRegisteredUsers); Task GetByOrganizationAsync(Guid organizationId, Guid userId); Task>> GetByIdWithCollectionsAsync(Guid id); diff --git a/src/Core/Services/IOrganizationService.cs b/src/Core/Services/IOrganizationService.cs index 5383c1a3e3..8082ed0f44 100644 --- a/src/Core/Services/IOrganizationService.cs +++ b/src/Core/Services/IOrganizationService.cs @@ -20,7 +20,6 @@ public interface IOrganizationService Task> SignUpAsync(OrganizationSignup organizationSignup, bool provider = false); Task> SignUpAsync(OrganizationLicense license, User owner, string ownerKey, string collectionName, string publicKey, string privateKey); - Task UpdateLicenseAsync(Guid organizationId, OrganizationLicense license); Task DeleteAsync(Organization organization); Task EnableAsync(Guid organizationId, DateTime? expirationDate); Task DisableAsync(Guid organizationId, DateTime? expirationDate); @@ -70,5 +69,5 @@ public interface IOrganizationService Task RestoreUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser, IUserService userService); Task>> RestoreUsersAsync(Guid organizationId, IEnumerable organizationUserIds, Guid? restoringUserId, IUserService userService); - Task GetOccupiedSeatCount(Organization organization); + Task ReplaceAndUpdateCacheAsync(Organization org, EventType? orgEvent = null); } diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index 88370639ec..0c0154c2de 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -39,7 +39,6 @@ public class OrganizationService : IOrganizationService private readonly ISsoUserRepository _ssoUserRepository; private readonly IReferenceEventService _referenceEventService; private readonly IGlobalSettings _globalSettings; - private readonly ITaxRateRepository _taxRateRepository; private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository; private readonly IOrganizationConnectionRepository _organizationConnectionRepository; private readonly ICurrentContext _currentContext; @@ -68,7 +67,6 @@ public class OrganizationService : IOrganizationService ISsoUserRepository ssoUserRepository, IReferenceEventService referenceEventService, IGlobalSettings globalSettings, - ITaxRateRepository taxRateRepository, IOrganizationApiKeyRepository organizationApiKeyRepository, IOrganizationConnectionRepository organizationConnectionRepository, ICurrentContext currentContext, @@ -96,7 +94,6 @@ public class OrganizationService : IOrganizationService _ssoUserRepository = ssoUserRepository; _referenceEventService = referenceEventService; _globalSettings = globalSettings; - _taxRateRepository = taxRateRepository; _organizationApiKeyRepository = organizationApiKeyRepository; _organizationConnectionRepository = organizationConnectionRepository; _currentContext = currentContext; @@ -119,7 +116,7 @@ public class OrganizationService : IOrganizationService paymentMethodType, paymentToken); if (updated) { - await ReplaceAndUpdateCache(organization); + await ReplaceAndUpdateCacheAsync(organization); } } @@ -205,7 +202,7 @@ public class OrganizationService : IOrganizationService (newPlan.HasAdditionalSeatsOption ? upgrade.AdditionalSeats : 0)); if (!organization.Seats.HasValue || organization.Seats.Value > newPlanSeats) { - var occupiedSeats = await GetOccupiedSeatCount(organization); + var occupiedSeats = await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); if (occupiedSeats > newPlanSeats) { throw new BadRequestException($"Your organization currently has {occupiedSeats} seats filled. " + @@ -346,7 +343,7 @@ public class OrganizationService : IOrganizationService organization.Enabled = success; organization.PublicKey = upgrade.PublicKey; organization.PrivateKey = upgrade.PrivateKey; - await ReplaceAndUpdateCache(organization); + await ReplaceAndUpdateCacheAsync(organization); if (success) { await _referenceEventService.RaiseEventAsync( @@ -392,7 +389,7 @@ public class OrganizationService : IOrganizationService PlanType = plan.Type, Storage = storageAdjustmentGb, }); - await ReplaceAndUpdateCache(organization); + await ReplaceAndUpdateCacheAsync(organization); return secret; } @@ -451,7 +448,7 @@ public class OrganizationService : IOrganizationService organization.MaxAutoscaleSeats = maxAutoscaleSeats; - await ReplaceAndUpdateCache(organization); + await ReplaceAndUpdateCacheAsync(organization); } public async Task AdjustSeatsAsync(Guid organizationId, int seatAdjustment, DateTime? prorationDate = null) @@ -513,7 +510,7 @@ public class OrganizationService : IOrganizationService if (!organization.Seats.HasValue || organization.Seats.Value > newSeatTotal) { - var occupiedSeats = await GetOccupiedSeatCount(organization); + var occupiedSeats = await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); if (occupiedSeats > newSeatTotal) { throw new BadRequestException($"Your organization currently has {occupiedSeats} seats filled. " + @@ -531,7 +528,7 @@ public class OrganizationService : IOrganizationService PreviousSeats = organization.Seats }); organization.Seats = (short?)newSeatTotal; - await ReplaceAndUpdateCache(organization); + await ReplaceAndUpdateCacheAsync(organization); if (organization.Seats.HasValue && organization.MaxAutoscaleSeats.HasValue && organization.Seats == organization.MaxAutoscaleSeats) { @@ -697,21 +694,10 @@ public class OrganizationService : IOrganizationService OrganizationLicense license, User owner, string ownerKey, string collectionName, string publicKey, string privateKey) { - if (license?.LicenseType != null && license.LicenseType != LicenseType.Organization) + var canUse = license.CanUse(_globalSettings, _licensingService, out var exception); + if (!canUse) { - throw new BadRequestException("Premium licenses cannot be applied to an organization. " - + "Upload this license from your personal account settings page."); - } - - if (license == null || !_licensingService.VerifyLicense(license)) - { - throw new BadRequestException("Invalid license."); - } - - if (!license.CanUse(_globalSettings)) - { - throw new BadRequestException("Invalid license. Make sure your license allows for on-premise " + - "hosting of organizations and that the installation id matches your current installation."); + throw new BadRequestException(exception); } if (license.PlanType != PlanType.Custom && @@ -843,172 +829,6 @@ public class OrganizationService : IOrganizationService } } - public async Task UpdateLicenseAsync(Guid organizationId, OrganizationLicense license) - { - var organization = await GetOrgById(organizationId); - if (organization == null) - { - throw new NotFoundException(); - } - - if (!_globalSettings.SelfHosted) - { - throw new InvalidOperationException("Licenses require self hosting."); - } - - if (license?.LicenseType != null && license.LicenseType != LicenseType.Organization) - { - throw new BadRequestException("Premium licenses cannot be applied to an organization. " - + "Upload this license from your personal account settings page."); - } - - if (license == null || !_licensingService.VerifyLicense(license)) - { - throw new BadRequestException("Invalid license."); - } - - if (!license.CanUse(_globalSettings)) - { - throw new BadRequestException("Invalid license. Make sure your license allows for on-premise " + - "hosting of organizations and that the installation id matches your current installation."); - } - - var enabledOrgs = await _organizationRepository.GetManyByEnabledAsync(); - if (enabledOrgs.Any(o => string.Equals(o.LicenseKey, license.LicenseKey) && o.Id != organizationId)) - { - throw new BadRequestException("License is already in use by another organization."); - } - - if (license.Seats.HasValue && - (!organization.Seats.HasValue || organization.Seats.Value > license.Seats.Value)) - { - var occupiedSeats = await GetOccupiedSeatCount(organization); - if (occupiedSeats > license.Seats.Value) - { - throw new BadRequestException($"Your organization currently has {occupiedSeats} seats filled. " + - $"Your new license only has ({license.Seats.Value}) seats. Remove some users."); - } - } - - if (license.MaxCollections.HasValue && (!organization.MaxCollections.HasValue || - organization.MaxCollections.Value > license.MaxCollections.Value)) - { - var collectionCount = await _collectionRepository.GetCountByOrganizationIdAsync(organization.Id); - if (collectionCount > license.MaxCollections.Value) - { - throw new BadRequestException($"Your organization currently has {collectionCount} collections. " + - $"Your new license allows for a maximum of ({license.MaxCollections.Value}) collections. " + - "Remove some collections."); - } - } - - if (!license.UseGroups && organization.UseGroups) - { - var groups = await _groupRepository.GetManyByOrganizationIdAsync(organization.Id); - if (groups.Count > 0) - { - throw new BadRequestException($"Your organization currently has {groups.Count} groups. " + - $"Your new license does not allow for the use of groups. Remove all groups."); - } - } - - if (!license.UsePolicies && organization.UsePolicies) - { - var policies = await _policyRepository.GetManyByOrganizationIdAsync(organization.Id); - if (policies.Any(p => p.Enabled)) - { - throw new BadRequestException($"Your organization currently has {policies.Count} enabled " + - $"policies. Your new license does not allow for the use of policies. Disable all policies."); - } - } - - if (!license.UseSso && organization.UseSso) - { - var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id); - if (ssoConfig != null && ssoConfig.Enabled) - { - throw new BadRequestException($"Your organization currently has a SSO configuration. " + - $"Your new license does not allow for the use of SSO. Disable your SSO configuration."); - } - } - - if (!license.UseKeyConnector && organization.UseKeyConnector) - { - var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id); - if (ssoConfig != null && ssoConfig.GetData().KeyConnectorEnabled) - { - throw new BadRequestException($"Your organization currently has Key Connector enabled. " + - $"Your new license does not allow for the use of Key Connector. Disable your Key Connector."); - } - } - - if (!license.UseScim && organization.UseScim) - { - var scimConnections = await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organization.Id, - OrganizationConnectionType.Scim); - if (scimConnections != null && scimConnections.Any(c => c.GetConfig()?.Enabled == true)) - { - throw new BadRequestException("Your new plan does not allow the SCIM feature. " + - "Disable your SCIM configuration."); - } - } - - if (!license.UseCustomPermissions && organization.UseCustomPermissions) - { - var organizationCustomUsers = - await _organizationUserRepository.GetManyByOrganizationAsync(organization.Id, - OrganizationUserType.Custom); - if (organizationCustomUsers.Any()) - { - throw new BadRequestException("Your new plan does not allow the Custom Permissions feature. " + - "Disable your Custom Permissions configuration."); - } - } - - if (!license.UseResetPassword && organization.UseResetPassword) - { - var resetPasswordPolicy = - await _policyRepository.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.ResetPassword); - if (resetPasswordPolicy != null && resetPasswordPolicy.Enabled) - { - throw new BadRequestException("Your new license does not allow the Password Reset feature. " - + "Disable your Password Reset policy."); - } - } - - var dir = $"{_globalSettings.LicenseDirectory}/organization"; - Directory.CreateDirectory(dir); - await using var fs = new FileStream(Path.Combine(dir, $"{organization.Id}.json"), FileMode.Create); - await JsonSerializer.SerializeAsync(fs, license, JsonHelpers.Indented); - - organization.Name = license.Name; - organization.BusinessName = license.BusinessName; - organization.BillingEmail = license.BillingEmail; - organization.PlanType = license.PlanType; - organization.Seats = license.Seats; - organization.MaxCollections = license.MaxCollections; - organization.UseGroups = license.UseGroups; - organization.UseDirectory = license.UseDirectory; - organization.UseEvents = license.UseEvents; - organization.UseTotp = license.UseTotp; - organization.Use2fa = license.Use2fa; - organization.UseApi = license.UseApi; - organization.UsePolicies = license.UsePolicies; - organization.UseSso = license.UseSso; - organization.UseKeyConnector = license.UseKeyConnector; - organization.UseScim = license.UseScim; - organization.UseResetPassword = license.UseResetPassword; - organization.SelfHost = license.SelfHost; - organization.UsersGetPremium = license.UsersGetPremium; - organization.UseCustomPermissions = license.UseCustomPermissions; - organization.Plan = license.Plan; - organization.Enabled = license.Enabled; - organization.ExpirationDate = license.Expires; - organization.LicenseKey = license.LicenseKey; - organization.RevisionDate = DateTime.UtcNow; - await ReplaceAndUpdateCache(organization); - } - public async Task DeleteAsync(Organization organization) { await ValidateDeleteOrganizationAsync(organization); @@ -1038,7 +858,7 @@ public class OrganizationService : IOrganizationService org.Enabled = true; org.ExpirationDate = expirationDate; org.RevisionDate = DateTime.UtcNow; - await ReplaceAndUpdateCache(org); + await ReplaceAndUpdateCacheAsync(org); } } @@ -1050,7 +870,7 @@ public class OrganizationService : IOrganizationService org.Enabled = false; org.ExpirationDate = expirationDate; org.RevisionDate = DateTime.UtcNow; - await ReplaceAndUpdateCache(org); + await ReplaceAndUpdateCacheAsync(org); // TODO: send email to owners? } @@ -1063,7 +883,7 @@ public class OrganizationService : IOrganizationService { org.ExpirationDate = expirationDate; org.RevisionDate = DateTime.UtcNow; - await ReplaceAndUpdateCache(org); + await ReplaceAndUpdateCacheAsync(org); } } @@ -1073,7 +893,7 @@ public class OrganizationService : IOrganizationService if (org != null && !org.Enabled) { org.Enabled = true; - await ReplaceAndUpdateCache(org); + await ReplaceAndUpdateCacheAsync(org); } } @@ -1093,7 +913,7 @@ public class OrganizationService : IOrganizationService } } - await ReplaceAndUpdateCache(organization, EventType.Organization_Updated); + await ReplaceAndUpdateCacheAsync(organization, EventType.Organization_Updated); if (updateBilling && !string.IsNullOrWhiteSpace(organization.GatewayCustomerId)) { @@ -1193,7 +1013,7 @@ public class OrganizationService : IOrganizationService organizationId, invites.SelectMany(i => i.invite.Emails), false), StringComparer.InvariantCultureIgnoreCase); if (organization.Seats.HasValue) { - var occupiedSeats = await GetOccupiedSeatCount(organization); + var occupiedSeats = await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); var availableSeats = organization.Seats.Value - occupiedSeats; newSeatsRequired = invites.Sum(i => i.invite.Emails.Count()) - existingEmails.Count() - availableSeats; } @@ -2044,7 +1864,7 @@ public class OrganizationService : IOrganizationService var enoughSeatsAvailable = true; if (organization.Seats.HasValue) { - var occupiedSeats = await GetOccupiedSeatCount(organization); + var occupiedSeats = await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); seatsAvailable = organization.Seats.Value - occupiedSeats; enoughSeatsAvailable = seatsAvailable >= usersToAdd.Count; } @@ -2213,7 +2033,7 @@ public class OrganizationService : IOrganizationService return devices.Where(d => !string.IsNullOrWhiteSpace(d.PushToken)).Select(d => d.Id.ToString()); } - private async Task ReplaceAndUpdateCache(Organization org, EventType? orgEvent = null) + public async Task ReplaceAndUpdateCacheAsync(Organization org, EventType? orgEvent = null) { await _organizationRepository.ReplaceAsync(org); await _applicationCacheService.UpsertOrganizationAbilityAsync(org); @@ -2463,7 +2283,7 @@ public class OrganizationService : IOrganizationService } var organization = await _organizationRepository.GetByIdAsync(organizationUser.OrganizationId); - var occupiedSeats = await GetOccupiedSeatCount(organization); + var occupiedSeats = await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); var availableSeats = organization.Seats.GetValueOrDefault(0) - occupiedSeats; if (availableSeats < 1) { @@ -2491,7 +2311,7 @@ public class OrganizationService : IOrganizationService } var organization = await _organizationRepository.GetByIdAsync(organizationId); - var occupiedSeats = await GetOccupiedSeatCount(organization); + var occupiedSeats = await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id); var availableSeats = organization.Seats.GetValueOrDefault(0) - occupiedSeats; var newSeatsRequired = organizationUserIds.Count() - availableSeats; await AutoAddSeatsAsync(organization, newSeatsRequired, DateTime.UtcNow); @@ -2606,10 +2426,4 @@ public class OrganizationService : IOrganizationService return status; } - - public async Task GetOccupiedSeatCount(Organization organization) - { - var orgUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(organization.Id); - return orgUsers.Count(ou => ou.OccupiesOrganizationSeat); - } } diff --git a/src/Infrastructure.Dapper/Repositories/OrganizationRepository.cs b/src/Infrastructure.Dapper/Repositories/OrganizationRepository.cs index c10903e116..52f486212f 100644 --- a/src/Infrastructure.Dapper/Repositories/OrganizationRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/OrganizationRepository.cs @@ -94,4 +94,44 @@ public class OrganizationRepository : Repository, IOrganizat return results.ToList(); } } + + public async Task GetByLicenseKeyAsync(string licenseKey) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryAsync( + "[dbo].[Organization_ReadByLicenseKey]", + new { LicenseKey = licenseKey }, + commandType: CommandType.StoredProcedure); + + return result.SingleOrDefault(); + } + } + + public async Task GetSelfHostedOrganizationDetailsById(Guid id) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.QueryMultipleAsync( + "[dbo].[Organization_ReadSelfHostedDetailsById]", + new { Id = id }, + commandType: CommandType.StoredProcedure); + + var selfHostOrganization = await result.ReadSingleOrDefaultAsync(); + if (selfHostOrganization == null) + { + return null; + } + + selfHostOrganization.OccupiedSeatCount = await result.ReadSingleAsync(); + selfHostOrganization.CollectionCount = await result.ReadSingleAsync(); + selfHostOrganization.GroupCount = await result.ReadSingleAsync(); + selfHostOrganization.OrganizationUsers = await result.ReadAsync(); + selfHostOrganization.Policies = await result.ReadAsync(); + selfHostOrganization.SsoConfig = await result.ReadFirstOrDefaultAsync(); + selfHostOrganization.ScimConnections = await result.ReadAsync(); + + return selfHostOrganization; + } + } } diff --git a/src/Infrastructure.Dapper/Repositories/OrganizationUserRepository.cs b/src/Infrastructure.Dapper/Repositories/OrganizationUserRepository.cs index ef3d5bfbbd..462f1ba065 100644 --- a/src/Infrastructure.Dapper/Repositories/OrganizationUserRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/OrganizationUserRepository.cs @@ -86,6 +86,19 @@ public class OrganizationUserRepository : Repository, IO } } + public async Task GetOccupiedSeatCountByOrganizationIdAsync(Guid organizationId) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var result = await connection.ExecuteScalarAsync( + "[dbo].[OrganizationUser_ReadOccupiedSeatCountByOrganizationId]", + new { OrganizationId = organizationId }, + commandType: CommandType.StoredProcedure); + + return result; + } + } + public async Task> SelectKnownEmailsAsync(Guid organizationId, IEnumerable emails, bool onlyRegisteredUsers) { diff --git a/src/Infrastructure.EntityFramework/Models/Organization.cs b/src/Infrastructure.EntityFramework/Models/Organization.cs index 49f9ff422c..fca548545c 100644 --- a/src/Infrastructure.EntityFramework/Models/Organization.cs +++ b/src/Infrastructure.EntityFramework/Models/Organization.cs @@ -8,6 +8,7 @@ public class Organization : Core.Entities.Organization public virtual ICollection OrganizationUsers { get; set; } public virtual ICollection Groups { get; set; } public virtual ICollection Policies { get; set; } + public virtual ICollection Collections { get; set; } public virtual ICollection SsoConfigs { get; set; } public virtual ICollection SsoUsers { get; set; } public virtual ICollection Transactions { get; set; } diff --git a/src/Infrastructure.EntityFramework/Repositories/OrganizationRepository.cs b/src/Infrastructure.EntityFramework/Repositories/OrganizationRepository.cs index 970a2da730..7f0028d25b 100644 --- a/src/Infrastructure.EntityFramework/Repositories/OrganizationRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/OrganizationRepository.cs @@ -1,9 +1,10 @@ using AutoMapper; +using Bit.Core.Enums; using Bit.Core.Models.Data.Organizations; using Bit.Core.Repositories; -using Bit.Infrastructure.EntityFramework.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Organization = Bit.Infrastructure.EntityFramework.Models.Organization; namespace Bit.Infrastructure.EntityFramework.Repositories; @@ -139,4 +140,40 @@ public class OrganizationRepository : Repository GetByLicenseKeyAsync(string licenseKey) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var organization = await GetDbSet(dbContext) + .FirstOrDefaultAsync(o => o.LicenseKey == licenseKey); + + return organization; + } + } + + public async Task GetSelfHostedOrganizationDetailsById(Guid id) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var organization = await GetDbSet(dbContext).FindAsync(id); + if (organization == null) + { + return null; + } + + var selfHostOrganization = Mapper.Map(organization); + + selfHostOrganization.OccupiedSeatCount = + organization.OrganizationUsers.Count(ou => ou.Status >= OrganizationUserStatusType.Invited); + selfHostOrganization.CollectionCount = organization.Collections?.Count ?? 0; + selfHostOrganization.GroupCount = organization?.Groups.Count ?? 0; + selfHostOrganization.SsoConfig = organization.SsoConfigs.SingleOrDefault(); + selfHostOrganization.ScimConnections = organization.Connections.Where(c => c.Type == OrganizationConnectionType.Scim); + + return selfHostOrganization; + } + } } diff --git a/src/Infrastructure.EntityFramework/Repositories/OrganizationUserRepository.cs b/src/Infrastructure.EntityFramework/Repositories/OrganizationUserRepository.cs index 6fe13f245d..8deae8e14d 100644 --- a/src/Infrastructure.EntityFramework/Repositories/OrganizationUserRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/OrganizationUserRepository.cs @@ -197,6 +197,12 @@ public class OrganizationUserRepository : Repository GetOccupiedSeatCountByOrganizationIdAsync(Guid organizationId) + { + var query = new OrganizationUserReadOccupiedSeatCountByOrganizationIdQuery(organizationId); + return await GetCountFromQuery(query); + } + public async Task GetCountByOrganizationIdAsync(Guid organizationId) { var query = new OrganizationUserReadCountByOrganizationIdQuery(organizationId); diff --git a/src/Infrastructure.EntityFramework/Repositories/Queries/OrganizationUserReadOccupiedSeatCountByOrganizationIdQuery.cs b/src/Infrastructure.EntityFramework/Repositories/Queries/OrganizationUserReadOccupiedSeatCountByOrganizationIdQuery.cs new file mode 100644 index 0000000000..47b50617a0 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Repositories/Queries/OrganizationUserReadOccupiedSeatCountByOrganizationIdQuery.cs @@ -0,0 +1,22 @@ +using Bit.Core.Enums; +using Bit.Infrastructure.EntityFramework.Models; + +namespace Bit.Infrastructure.EntityFramework.Repositories.Queries; + +public class OrganizationUserReadOccupiedSeatCountByOrganizationIdQuery : IQuery +{ + private readonly Guid _organizationId; + + public OrganizationUserReadOccupiedSeatCountByOrganizationIdQuery(Guid organizationId) + { + _organizationId = organizationId; + } + + public IQueryable Run(DatabaseContext dbContext) + { + var query = from ou in dbContext.OrganizationUsers + where ou.OrganizationId == _organizationId && ou.Status >= OrganizationUserStatusType.Invited + select ou; + return query; + } +} diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 1bed1049a3..39a4a06a92 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -454,6 +454,9 @@ + + + @@ -471,4 +474,4 @@ -
\ No newline at end of file +
diff --git a/src/Sql/dbo/Stored Procedures/Group_ReadCountByOrganizationId.sql b/src/Sql/dbo/Stored Procedures/Group_ReadCountByOrganizationId.sql new file mode 100644 index 0000000000..141143ee73 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Group_ReadCountByOrganizationId.sql @@ -0,0 +1,13 @@ +CREATE PROCEDURE [dbo].[Group_ReadCountByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + COUNT(1) + FROM + [dbo].[Group] + WHERE + [OrganizationId] = @OrganizationId +END diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_ReadOccupiedSeatCountByOrganizationId.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_ReadOccupiedSeatCountByOrganizationId.sql new file mode 100644 index 0000000000..892ed51012 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_ReadOccupiedSeatCountByOrganizationId.sql @@ -0,0 +1,14 @@ +CREATE PROCEDURE [dbo].[OrganizationUser_ReadOccupiedSeatCountByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + COUNT(1) + FROM + [dbo].[OrganizationUserView] + WHERE + OrganizationId = @OrganizationId + AND Status >= 0 --Invited +END diff --git a/src/Sql/dbo/Stored Procedures/Organization_ReadByLicenseKey.sql b/src/Sql/dbo/Stored Procedures/Organization_ReadByLicenseKey.sql new file mode 100644 index 0000000000..c189aedd30 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Organization_ReadByLicenseKey.sql @@ -0,0 +1,13 @@ +CREATE PROCEDURE [dbo].[Organization_ReadByLicenseKey] + @LicenseKey VARCHAR (100) +AS +BEGIN + SET NOCOUNT ON + +SELECT + * +FROM + [dbo].[OrganizationView] +WHERE + [LicenseKey] = @LicenseKey +END diff --git a/src/Sql/dbo/Stored Procedures/Organization_ReadSelfHostedDetailsById.sql b/src/Sql/dbo/Stored Procedures/Organization_ReadSelfHostedDetailsById.sql new file mode 100644 index 0000000000..6db79e0b2e --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Organization_ReadSelfHostedDetailsById.sql @@ -0,0 +1,15 @@ +CREATE PROCEDURE [dbo].[Organization_ReadSelfHostedDetailsById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[Organization_ReadById] @Id + EXEC [dbo].[OrganizationUser_ReadOccupiedSeatCountByOrganizationId] @Id + EXEC [dbo].[Collection_ReadCountByOrganizationId] @Id + EXEC [dbo].[Group_ReadCountByOrganizationId] @Id + EXEC [dbo].[OrganizationUser_ReadByOrganizationId] @Id, NULL + EXEC [dbo].[Policy_ReadByOrganizationId] @Id + EXEC [dbo].[SsoConfig_ReadByOrganizationId] @Id + EXEC [dbo].[OrganizationConnection_ReadByOrganizationIdType] @Id, 2 --Scim connection type +END diff --git a/test/Api.Test/Controllers/OrganizationsControllerTests.cs b/test/Api.Test/Controllers/OrganizationsControllerTests.cs index 8755388213..0933a6ae53 100644 --- a/test/Api.Test/Controllers/OrganizationsControllerTests.cs +++ b/test/Api.Test/Controllers/OrganizationsControllerTests.cs @@ -32,6 +32,7 @@ public class OrganizationsControllerTests : IDisposable private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository; private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery; private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand; + private readonly IUpdateOrganizationLicenseCommand _updateOrganizationLicenseCommand; private readonly IOrganizationDomainRepository _organizationDomainRepository; private readonly OrganizationsController _sut; @@ -53,11 +54,13 @@ public class OrganizationsControllerTests : IDisposable _userService = Substitute.For(); _cloudGetOrganizationLicenseQuery = Substitute.For(); _createOrganizationApiKeyCommand = Substitute.For(); + _updateOrganizationLicenseCommand = Substitute.For(); _sut = new OrganizationsController(_organizationRepository, _organizationUserRepository, _policyRepository, _organizationService, _userService, _paymentService, _currentContext, _ssoConfigRepository, _ssoConfigService, _getOrganizationApiKeyQuery, _rotateOrganizationApiKeyCommand, - _createOrganizationApiKeyCommand, _organizationApiKeyRepository, _cloudGetOrganizationLicenseQuery, _globalSettings); + _createOrganizationApiKeyCommand, _organizationApiKeyRepository, _updateOrganizationLicenseCommand, + _cloudGetOrganizationLicenseQuery, _globalSettings); } public void Dispose() diff --git a/test/Core.Test/Models/Data/SelfHostedOrganizationDetailsTests.cs b/test/Core.Test/Models/Data/SelfHostedOrganizationDetailsTests.cs new file mode 100644 index 0000000000..02c35151c2 --- /dev/null +++ b/test/Core.Test/Models/Data/SelfHostedOrganizationDetailsTests.cs @@ -0,0 +1,379 @@ +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Models.Business; +using Bit.Core.Models.Data; +using Bit.Core.Models.Data.Organizations; +using Bit.Core.Models.OrganizationConnectionConfigs; +using Bit.Core.Test.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Xunit; + +namespace Bit.Core.Test.Models.Data; + +public class SelfHostedOrganizationDetailsTests +{ + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_Success(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.True(result); + Assert.True(string.IsNullOrEmpty(exception)); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_OccupiedSeatCount_ExceedsLicense_Fail(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.Seats = 1; + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.False(result); + Assert.Contains("Remove some users", exception); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_MaxCollections_ExceedsLicense_Fail(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.MaxCollections = 1; + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.False(result); + Assert.Contains("Remove some collections", exception); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_Groups_NotAllowedByLicense_Fail(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.UseGroups = false; + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.False(result); + Assert.Contains("Your new license does not allow for the use of groups", exception); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_Policies_NotAllowedByLicense_Fail(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.UsePolicies = false; + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.False(result); + Assert.Contains("Your new license does not allow for the use of policies", exception); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_DisabledPolicies_NotAllowedByLicense_Success(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.UsePolicies = false; + ((List)orgDetails.Policies).ForEach(p => p.Enabled = false); + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.True(result); + Assert.True(string.IsNullOrEmpty(exception)); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_Sso_NotAllowedByLicense_Fail(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.UseSso = false; + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.False(result); + Assert.Contains("Your new license does not allow for the use of SSO", exception); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_DisabledSso_NotAllowedByLicense_Success(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.UseSso = false; + orgDetails.SsoConfig.Enabled = false; + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.True(result); + Assert.True(string.IsNullOrEmpty(exception)); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_NoSso_NotAllowedByLicense_Success(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.UseSso = false; + orgDetails.SsoConfig = null; + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.True(result); + Assert.True(string.IsNullOrEmpty(exception)); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_KeyConnector_NotAllowedByLicense_Fail(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.UseKeyConnector = false; + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.False(result); + Assert.Contains("Your new license does not allow for the use of Key Connector", exception); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_DisabledKeyConnector_NotAllowedByLicense_Success(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.UseKeyConnector = false; + orgDetails.SsoConfig.SetData(new SsoConfigurationData() { KeyConnectorEnabled = false }); + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.True(result); + Assert.True(string.IsNullOrEmpty(exception)); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_NoSsoKeyConnector_NotAllowedByLicense_Success(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.UseKeyConnector = false; + orgDetails.SsoConfig = null; + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.True(result); + Assert.True(string.IsNullOrEmpty(exception)); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_Scim_NotAllowedByLicense_Fail(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.UseScim = false; + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.False(result); + Assert.Contains("Your new plan does not allow the SCIM feature", exception); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_DisabledScim_NotAllowedByLicense_Success(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.UseScim = false; + ((List>)orgDetails.ScimConnections) + .ForEach(c => c.SetConfig(new ScimConfig() { Enabled = false })); + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.True(result); + Assert.True(string.IsNullOrEmpty(exception)); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_NoScimConfig_NotAllowedByLicense_Success(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.UseScim = false; + orgDetails.ScimConnections = null; + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.True(result); + Assert.True(string.IsNullOrEmpty(exception)); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_CustomPermissions_NotAllowedByLicense_Fail(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.UseCustomPermissions = false; + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.False(result); + Assert.Contains("Your new plan does not allow the Custom Permissions feature", exception); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_NoCustomPermissions_NotAllowedByLicense_Success(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.UseCustomPermissions = false; + ((List)orgDetails.OrganizationUsers).ForEach(ou => ou.Type = OrganizationUserType.User); + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.True(result); + Assert.True(string.IsNullOrEmpty(exception)); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_ResetPassword_NotAllowedByLicense_Fail(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.UseResetPassword = false; + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.False(result); + Assert.Contains("Your new license does not allow the Password Reset feature", exception); + } + + [Theory] + [BitAutoData] + [OrganizationLicenseCustomize] + public async Task ValidateForOrganization_DisabledResetPassword_NotAllowedByLicense_Success(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); + orgLicense.UseResetPassword = false; + ((List)orgDetails.Policies).ForEach(p => p.Enabled = false); + + var result = orgDetails.CanUseLicense(license, out var exception); + + Assert.True(result); + Assert.True(string.IsNullOrEmpty(exception)); + } + + private (SelfHostedOrganizationDetails organization, OrganizationLicense license) GetOrganizationAndLicense(List orgUsers, + List policies, SsoConfig ssoConfig, List> scimConnections, OrganizationLicense license) + { + // The default state is that all features are used by Org and allowed by License + // Each test then toggles on/off as necessary + policies.ForEach(p => p.Enabled = true); + policies.First().Type = PolicyType.ResetPassword; + + ssoConfig.Enabled = true; + ssoConfig.SetData(new SsoConfigurationData() + { + KeyConnectorEnabled = true + }); + + var enabledScimConfig = new ScimConfig() { Enabled = true }; + scimConnections.ForEach(c => c.Config = enabledScimConfig); + + orgUsers.First().Type = OrganizationUserType.Custom; + + var organization = new SelfHostedOrganizationDetails() + { + OccupiedSeatCount = 10, + CollectionCount = 5, + GroupCount = 5, + OrganizationUsers = orgUsers, + Policies = policies, + SsoConfig = ssoConfig, + ScimConnections = scimConnections, + + UsePolicies = true, + UseSso = true, + UseKeyConnector = true, + UseScim = true, + UseGroups = true, + UseDirectory = true, + UseEvents = true, + UseTotp = true, + Use2fa = true, + UseApi = true, + UseResetPassword = true, + SelfHost = true, + UsersGetPremium = true, + UseCustomPermissions = true, + }; + + license.Enabled = true; + license.PlanType = PlanType.EnterpriseAnnually; + license.Seats = 10; + license.MaxCollections = 5; + license.UsePolicies = true; + license.UseSso = true; + license.UseKeyConnector = true; + license.UseScim = true; + license.UseGroups = true; + license.UseEvents = true; + license.UseDirectory = true; + license.UseTotp = true; + license.Use2fa = true; + license.UseApi = true; + license.UseResetPassword = true; + license.MaxStorageGb = 1; + license.SelfHost = true; + license.UsersGetPremium = true; + license.UseCustomPermissions = true; + license.Version = 11; + license.Issued = DateTime.Now; + license.Expires = DateTime.Now.AddYears(1); + + return (organization, license); + } +} diff --git a/util/Migrator/DbScripts/2023-02-16_00_SelfHostedOrganizationDetails.sql b/util/Migrator/DbScripts/2023-02-16_00_SelfHostedOrganizationDetails.sql new file mode 100644 index 0000000000..49323874cc --- /dev/null +++ b/util/Migrator/DbScripts/2023-02-16_00_SelfHostedOrganizationDetails.sql @@ -0,0 +1,62 @@ +CREATE OR ALTER PROCEDURE [dbo].[Group_ReadCountByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + COUNT(1) + FROM + [dbo].[Group] + WHERE + [OrganizationId] = @OrganizationId +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_ReadOccupiedSeatCountByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + COUNT(1) + FROM + [dbo].[OrganizationUserView] + WHERE + OrganizationId = @OrganizationId + AND Status >= 0 --Invited +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[Organization_ReadByLicenseKey] + @LicenseKey VARCHAR (100) +AS +BEGIN + SET NOCOUNT ON + +SELECT + * +FROM + [dbo].[OrganizationView] +WHERE + [LicenseKey] = @LicenseKey +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[Organization_ReadSelfHostedDetailsById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[Organization_ReadById] @Id + EXEC [dbo].[OrganizationUser_ReadOccupiedSeatCountByOrganizationId] @Id + EXEC [dbo].[Collection_ReadCountByOrganizationId] @Id + EXEC [dbo].[Group_ReadCountByOrganizationId] @Id + EXEC [dbo].[OrganizationUser_ReadByOrganizationId] @Id, NULL + EXEC [dbo].[Policy_ReadByOrganizationId] @Id + EXEC [dbo].[SsoConfig_ReadByOrganizationId] @Id + EXEC [dbo].[OrganizationConnection_ReadByOrganizationIdType] @Id, 2 --Scim connection type +END +GO From 64e0a981c9e746572e9373d981e77198c1a9a8f6 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 24 Feb 2023 16:44:33 +0100 Subject: [PATCH 47/49] [SM-389] Event log for service account (#2674) --- src/Api/Models/Response/EventResponseModel.cs | 4 + .../Controllers/SecretsController.cs | 23 +- src/Core/Entities/Event.cs | 5 +- src/Core/Enums/EventType.cs | 4 +- src/Core/Models/Data/EventMessage.cs | 2 + src/Core/Models/Data/EventTableEntity.cs | 22 + src/Core/Models/Data/IEvent.cs | 2 + src/Core/Services/IEventService.cs | 2 + .../Services/Implementations/EventService.cs | 24 +- .../NoopImplementations/NoopEventService.cs | 6 + .../dbo/Stored Procedures/Event_Create.sql | 12 +- src/Sql/dbo/Tables/Event.sql | 4 +- .../2023-02-16_00_SecretsManagerEvent.sql | 100 + ...0213133250_SecretsManagerEvent.Designer.cs | 2138 ++++++++++++++++ .../20230213133250_SecretsManagerEvent.cs | 36 + .../DatabaseContextModelSnapshot.cs | 6 + ...0213133239_SecretsManagerEvent.Designer.cs | 2149 +++++++++++++++++ .../20230213133239_SecretsManagerEvent.cs | 34 + .../DatabaseContextModelSnapshot.cs | 6 + ...0213133244_SecretsManagerEvent.Designer.cs | 2136 ++++++++++++++++ .../20230213133244_SecretsManagerEvent.cs | 34 + .../DatabaseContextModelSnapshot.cs | 6 + 22 files changed, 6744 insertions(+), 11 deletions(-) create mode 100644 util/Migrator/DbScripts/2023-02-16_00_SecretsManagerEvent.sql create mode 100644 util/MySqlMigrations/Migrations/20230213133250_SecretsManagerEvent.Designer.cs create mode 100644 util/MySqlMigrations/Migrations/20230213133250_SecretsManagerEvent.cs create mode 100644 util/PostgresMigrations/Migrations/20230213133239_SecretsManagerEvent.Designer.cs create mode 100644 util/PostgresMigrations/Migrations/20230213133239_SecretsManagerEvent.cs create mode 100644 util/SqliteMigrations/Migrations/20230213133244_SecretsManagerEvent.Designer.cs create mode 100644 util/SqliteMigrations/Migrations/20230213133244_SecretsManagerEvent.cs diff --git a/src/Api/Models/Response/EventResponseModel.cs b/src/Api/Models/Response/EventResponseModel.cs index bd94dd171f..68695b3ab8 100644 --- a/src/Api/Models/Response/EventResponseModel.cs +++ b/src/Api/Models/Response/EventResponseModel.cs @@ -32,6 +32,8 @@ public class EventResponseModel : ResponseModel InstallationId = ev.InstallationId; SystemUser = ev.SystemUser; DomainName = ev.DomainName; + SecretId = ev.SecretId; + ServiceAccountId = ev.ServiceAccountId; } public EventType Type { get; set; } @@ -52,4 +54,6 @@ public class EventResponseModel : ResponseModel public string IpAddress { get; set; } public EventSystemUser? SystemUser { get; set; } public string DomainName { get; set; } + public Guid? SecretId { get; set; } + public Guid? ServiceAccountId { get; set; } } diff --git a/src/Api/SecretsManager/Controllers/SecretsController.cs b/src/Api/SecretsManager/Controllers/SecretsController.cs index 5c91c2e807..9cf0a91edd 100644 --- a/src/Api/SecretsManager/Controllers/SecretsController.cs +++ b/src/Api/SecretsManager/Controllers/SecretsController.cs @@ -4,6 +4,7 @@ using Bit.Api.SecretsManager.Models.Response; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Exceptions; +using Bit.Core.Identity; using Bit.Core.SecretsManager.Commands.Secrets.Interfaces; using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Repositories; @@ -18,22 +19,32 @@ namespace Bit.Api.SecretsManager.Controllers; public class SecretsController : Controller { private readonly ICurrentContext _currentContext; - private readonly ISecretRepository _secretRepository; private readonly IProjectRepository _projectRepository; + private readonly ISecretRepository _secretRepository; private readonly ICreateSecretCommand _createSecretCommand; private readonly IUpdateSecretCommand _updateSecretCommand; private readonly IDeleteSecretCommand _deleteSecretCommand; private readonly IUserService _userService; + private readonly IEventService _eventService; - public SecretsController(ISecretRepository secretRepository, IProjectRepository projectRepository, ICreateSecretCommand createSecretCommand, IUpdateSecretCommand updateSecretCommand, IDeleteSecretCommand deleteSecretCommand, IUserService userService, ICurrentContext currentContext) + public SecretsController( + ICurrentContext currentContext, + IProjectRepository projectRepository, + ISecretRepository secretRepository, + ICreateSecretCommand createSecretCommand, + IUpdateSecretCommand updateSecretCommand, + IDeleteSecretCommand deleteSecretCommand, + IUserService userService, + IEventService eventService) { _currentContext = currentContext; + _projectRepository = projectRepository; _secretRepository = secretRepository; _createSecretCommand = createSecretCommand; _updateSecretCommand = updateSecretCommand; _deleteSecretCommand = deleteSecretCommand; - _projectRepository = projectRepository; _userService = userService; + _eventService = eventService; } [HttpGet("organizations/{organizationId}/secrets")] @@ -81,6 +92,12 @@ public class SecretsController : Controller throw new NotFoundException(); } + if (_currentContext.ClientType == ClientType.ServiceAccount) + { + var userId = _userService.GetProperUserId(User).Value; + await _eventService.LogServiceAccountSecretEventAsync(userId, secret, EventType.Secret_Retrieved); + } + return new SecretResponseModel(secret); } diff --git a/src/Core/Entities/Event.cs b/src/Core/Entities/Event.cs index 5fa484a0fe..e666c76f76 100644 --- a/src/Core/Entities/Event.cs +++ b/src/Core/Entities/Event.cs @@ -29,6 +29,8 @@ public class Event : ITableObject, IEvent ActingUserId = e.ActingUserId; SystemUser = e.SystemUser; DomainName = e.DomainName; + SecretId = e.SecretId; + ServiceAccountId = e.ServiceAccountId; } public Guid Id { get; set; } @@ -51,7 +53,8 @@ public class Event : ITableObject, IEvent public Guid? ActingUserId { get; set; } public EventSystemUser? SystemUser { get; set; } public string DomainName { get; set; } - + public Guid? SecretId { get; set; } + public Guid? ServiceAccountId { get; set; } public void SetNewId() { diff --git a/src/Core/Enums/EventType.cs b/src/Core/Enums/EventType.cs index 77e62ed3ad..82339ac8a7 100644 --- a/src/Core/Enums/EventType.cs +++ b/src/Core/Enums/EventType.cs @@ -80,5 +80,7 @@ public enum EventType : int OrganizationDomain_Added = 2000, OrganizationDomain_Removed = 2001, OrganizationDomain_Verified = 2002, - OrganizationDomain_NotVerified = 2003 + OrganizationDomain_NotVerified = 2003, + + Secret_Retrieved = 2100, } diff --git a/src/Core/Models/Data/EventMessage.cs b/src/Core/Models/Data/EventMessage.cs index c969df53d2..6d2a1f2b4e 100644 --- a/src/Core/Models/Data/EventMessage.cs +++ b/src/Core/Models/Data/EventMessage.cs @@ -33,4 +33,6 @@ public class EventMessage : IEvent public Guid? IdempotencyId { get; private set; } = Guid.NewGuid(); public EventSystemUser? SystemUser { get; set; } public string DomainName { get; set; } + public Guid? SecretId { get; set; } + public Guid? ServiceAccountId { get; set; } } diff --git a/src/Core/Models/Data/EventTableEntity.cs b/src/Core/Models/Data/EventTableEntity.cs index 9e49d3f686..df4a85acaf 100644 --- a/src/Core/Models/Data/EventTableEntity.cs +++ b/src/Core/Models/Data/EventTableEntity.cs @@ -28,6 +28,8 @@ public class EventTableEntity : TableEntity, IEvent ActingUserId = e.ActingUserId; SystemUser = e.SystemUser; DomainName = e.DomainName; + SecretId = e.SecretId; + ServiceAccountId = e.ServiceAccountId; } public DateTime Date { get; set; } @@ -48,6 +50,8 @@ public class EventTableEntity : TableEntity, IEvent public Guid? ActingUserId { get; set; } public EventSystemUser? SystemUser { get; set; } public string DomainName { get; set; } + public Guid? SecretId { get; set; } + public Guid? ServiceAccountId { get; set; } public override IDictionary WriteEntity(OperationContext operationContext) { @@ -154,6 +158,24 @@ public class EventTableEntity : TableEntity, IEvent }); } + if (e.OrganizationId.HasValue && e.ServiceAccountId.HasValue) + { + entities.Add(new EventTableEntity(e) + { + PartitionKey = pKey, + RowKey = $"ServiceAccountId={e.ServiceAccountId}__Date={dateKey}__Uniquifier={uniquifier}" + }); + } + + if (e.SecretId.HasValue) + { + entities.Add(new EventTableEntity(e) + { + PartitionKey = pKey, + RowKey = $"SecretId={e.CipherId}__Date={dateKey}__Uniquifier={uniquifier}" + }); + } + return entities; } diff --git a/src/Core/Models/Data/IEvent.cs b/src/Core/Models/Data/IEvent.cs index 5c2e4b8364..6a177e39ca 100644 --- a/src/Core/Models/Data/IEvent.cs +++ b/src/Core/Models/Data/IEvent.cs @@ -22,4 +22,6 @@ public interface IEvent DateTime Date { get; set; } EventSystemUser? SystemUser { get; set; } string DomainName { get; set; } + Guid? SecretId { get; set; } + Guid? ServiceAccountId { get; set; } } diff --git a/src/Core/Services/IEventService.cs b/src/Core/Services/IEventService.cs index ec05c6943a..c22757f9c8 100644 --- a/src/Core/Services/IEventService.cs +++ b/src/Core/Services/IEventService.cs @@ -1,6 +1,7 @@ using Bit.Core.Entities; using Bit.Core.Entities.Provider; using Bit.Core.Enums; +using Bit.Core.SecretsManager.Entities; namespace Bit.Core.Services; @@ -25,4 +26,5 @@ public interface IEventService Task LogProviderOrganizationEventAsync(ProviderOrganization providerOrganization, EventType type, DateTime? date = null); Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, DateTime? date = null); Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, EventSystemUser systemUser, DateTime? date = null); + Task LogServiceAccountSecretEventAsync(Guid serviceAccountId, Secret secret, EventType type, DateTime? date = null); } diff --git a/src/Core/Services/Implementations/EventService.cs b/src/Core/Services/Implementations/EventService.cs index f86cff76f9..cf74767be1 100644 --- a/src/Core/Services/Implementations/EventService.cs +++ b/src/Core/Services/Implementations/EventService.cs @@ -5,6 +5,7 @@ using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Models.Data.Organizations; using Bit.Core.Repositories; +using Bit.Core.SecretsManager.Entities; using Bit.Core.Settings; namespace Bit.Core.Services; @@ -391,6 +392,25 @@ public class EventService : IEventService await _eventWriteService.CreateAsync(e); } + public async Task LogServiceAccountSecretEventAsync(Guid serviceAccountId, Secret secret, EventType type, DateTime? date = null) + { + var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); + if (!CanUseEvents(orgAbilities, secret.OrganizationId)) + { + return; + } + + var e = new EventMessage(_currentContext) + { + OrganizationId = secret.OrganizationId, + Type = type, + SecretId = secret.Id, + ServiceAccountId = serviceAccountId, + Date = date.GetValueOrDefault(DateTime.UtcNow) + }; + await _eventWriteService.CreateAsync(e); + } + private async Task GetProviderIdAsync(Guid? orgId) { if (_currentContext == null || !orgId.HasValue) @@ -414,12 +434,12 @@ public class EventService : IEventService private bool CanUseEvents(IDictionary orgAbilities, Guid orgId) { return orgAbilities != null && orgAbilities.ContainsKey(orgId) && - orgAbilities[orgId].Enabled && orgAbilities[orgId].UseEvents; + orgAbilities[orgId].Enabled && orgAbilities[orgId].UseEvents; } private bool CanUseProviderEvents(IDictionary providerAbilities, Guid providerId) { return providerAbilities != null && providerAbilities.ContainsKey(providerId) && - providerAbilities[providerId].Enabled && providerAbilities[providerId].UseEvents; + providerAbilities[providerId].Enabled && providerAbilities[providerId].UseEvents; } } diff --git a/src/Core/Services/NoopImplementations/NoopEventService.cs b/src/Core/Services/NoopImplementations/NoopEventService.cs index a44fdf3e2c..0f23933b77 100644 --- a/src/Core/Services/NoopImplementations/NoopEventService.cs +++ b/src/Core/Services/NoopImplementations/NoopEventService.cs @@ -1,6 +1,7 @@ using Bit.Core.Entities; using Bit.Core.Entities.Provider; using Bit.Core.Enums; +using Bit.Core.SecretsManager.Entities; namespace Bit.Core.Services; @@ -107,4 +108,9 @@ public class NoopEventService : IEventService return Task.FromResult(0); } + public Task LogServiceAccountSecretEventAsync(Guid serviceAccountId, Secret secret, EventType type, + DateTime? date = null) + { + return Task.FromResult(0); + } } diff --git a/src/Sql/dbo/Stored Procedures/Event_Create.sql b/src/Sql/dbo/Stored Procedures/Event_Create.sql index 910e0e5988..cd3dd6b6e9 100644 --- a/src/Sql/dbo/Stored Procedures/Event_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Event_Create.sql @@ -17,7 +17,9 @@ @IpAddress VARCHAR(50), @Date DATETIME2(7), @SystemUser TINYINT = null, - @DomainName VARCHAR(256) + @DomainName VARCHAR(256), + @SecretId UNIQUEIDENTIFIER = null, + @ServiceAccountId UNIQUEIDENTIFIER = null AS BEGIN SET NOCOUNT ON @@ -42,7 +44,9 @@ BEGIN [IpAddress], [Date], [SystemUser], - [DomainName] + [DomainName], + [SecretId], + [ServiceAccountId] ) VALUES ( @@ -64,6 +68,8 @@ BEGIN @IpAddress, @Date, @SystemUser, - @DomainName + @DomainName, + @SecretId, + @ServiceAccountId ) END diff --git a/src/Sql/dbo/Tables/Event.sql b/src/Sql/dbo/Tables/Event.sql index 5181d67686..1932f103f5 100644 --- a/src/Sql/dbo/Tables/Event.sql +++ b/src/Sql/dbo/Tables/Event.sql @@ -17,7 +17,9 @@ [ProviderUserId] UNIQUEIDENTIFIER NULL, [ProviderOrganizationId] UNIQUEIDENTIFIER NULL, [SystemUser] TINYINT NULL, - [DomainName] VARCHAR(256) NULL + [DomainName] VARCHAR(256) NULL, + [SecretId] UNIQUEIDENTIFIER NULL, + [ServiceAccountId] UNIQUEIDENTIFIER NULL, CONSTRAINT [PK_Event] PRIMARY KEY CLUSTERED ([Id] ASC) ); diff --git a/util/Migrator/DbScripts/2023-02-16_00_SecretsManagerEvent.sql b/util/Migrator/DbScripts/2023-02-16_00_SecretsManagerEvent.sql new file mode 100644 index 0000000000..14a6873695 --- /dev/null +++ b/util/Migrator/DbScripts/2023-02-16_00_SecretsManagerEvent.sql @@ -0,0 +1,100 @@ +IF COL_LENGTH('[dbo].[Event]', 'SecretId') IS NULL +BEGIN + ALTER TABLE + [dbo].[Event] + ADD + [SecretId] UNIQUEIDENTIFIER NULL +END +GO + +IF COL_LENGTH('[dbo].[Event]', 'ServiceAccountId') IS NULL +BEGIN + ALTER TABLE + [dbo].[Event] + ADD + [ServiceAccountId] UNIQUEIDENTIFIER NULL +END +GO + +IF OBJECT_ID('[dbo].[EventView]') IS NOT NULL +BEGIN + EXECUTE sp_refreshsqlmodule N'[dbo].[EventView]'; +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[Event_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @Type INT, + @UserId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @InstallationId UNIQUEIDENTIFIER, + @ProviderId UNIQUEIDENTIFIER, + @CipherId UNIQUEIDENTIFIER, + @CollectionId UNIQUEIDENTIFIER, + @PolicyId UNIQUEIDENTIFIER, + @GroupId UNIQUEIDENTIFIER, + @OrganizationUserId UNIQUEIDENTIFIER, + @ProviderUserId UNIQUEIDENTIFIER, + @ProviderOrganizationId UNIQUEIDENTIFIER = null, + @ActingUserId UNIQUEIDENTIFIER, + @DeviceType SMALLINT, + @IpAddress VARCHAR(50), + @Date DATETIME2(7), + @SystemUser TINYINT = null, + @DomainName VARCHAR(256), + @SecretId UNIQUEIDENTIFIER = null, + @ServiceAccountId UNIQUEIDENTIFIER = null +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Event] + ( + [Id], + [Type], + [UserId], + [OrganizationId], + [InstallationId], + [ProviderId], + [CipherId], + [CollectionId], + [PolicyId], + [GroupId], + [OrganizationUserId], + [ProviderUserId], + [ProviderOrganizationId], + [ActingUserId], + [DeviceType], + [IpAddress], + [Date], + [SystemUser], + [DomainName], + [SecretId], + [ServiceAccountId] + ) + VALUES + ( + @Id, + @Type, + @UserId, + @OrganizationId, + @InstallationId, + @ProviderId, + @CipherId, + @CollectionId, + @PolicyId, + @GroupId, + @OrganizationUserId, + @ProviderUserId, + @ProviderOrganizationId, + @ActingUserId, + @DeviceType, + @IpAddress, + @Date, + @SystemUser, + @DomainName, + @SecretId, + @ServiceAccountId + ) +END +GO diff --git a/util/MySqlMigrations/Migrations/20230213133250_SecretsManagerEvent.Designer.cs b/util/MySqlMigrations/Migrations/20230213133250_SecretsManagerEvent.Designer.cs new file mode 100644 index 0000000000..fb72ab0d7b --- /dev/null +++ b/util/MySqlMigrations/Migrations/20230213133250_SecretsManagerEvent.Designer.cs @@ -0,0 +1,2138 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20230213133250_SecretsManagerEvent")] + partial class SecretsManagerEvent + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.12") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("varchar(25)"); + + b.Property("Approved") + .HasColumnType("tinyint(1)"); + + b.Property("AuthenticationDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MasterPasswordHash") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("RequestFingerprint") + .HasColumnType("longtext"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ResponseDate") + .HasColumnType("datetime(6)"); + + b.Property("ResponseDeviceId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("InstallationId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.Property("SystemUser") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseCustomPermissions") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseScim") + .HasColumnType("tinyint(1)"); + + b.Property("UseSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Config") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("LastSyncDate") + .HasColumnType("datetime(6)"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("tinyint unsigned"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("char(36)"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("ToDelete") + .HasColumnType("tinyint(1)"); + + b.Property("ValidUntil") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property("AccessSecretsManager") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BillingPhone") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("varchar(7)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("FailedLoginCount") + .HasColumnType("int"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("KdfMemory") + .HasColumnType("int"); + + b.Property("KdfParallelism") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LastEmailChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastFailedLoginDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKdfChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LastKeyRotationDate") + .HasColumnType("datetime(6)"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("datetime(6)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UnknownDeviceVerificationEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Read") + .HasColumnType("tinyint(1)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Write") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator("Discriminator").HasValue("AccessPolicy"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ClientSecret") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("EncryptedPayload") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ExpireAt") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Scope") + .HasMaxLength(4000) + .HasColumnType("varchar(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Note") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("char(36)"); + + b.Property("SecretsId") + .HasColumnType("char(36)"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("char(36)") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId"); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Connections"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20230213133250_SecretsManagerEvent.cs b/util/MySqlMigrations/Migrations/20230213133250_SecretsManagerEvent.cs new file mode 100644 index 0000000000..91d12c745d --- /dev/null +++ b/util/MySqlMigrations/Migrations/20230213133250_SecretsManagerEvent.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +public partial class SecretsManagerEvent : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SecretId", + table: "Event", + type: "char(36)", + nullable: true, + collation: "ascii_general_ci"); + + migrationBuilder.AddColumn( + name: "ServiceAccountId", + table: "Event", + type: "char(36)", + nullable: true, + collation: "ascii_general_ci"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SecretId", + table: "Event"); + + migrationBuilder.DropColumn( + name: "ServiceAccountId", + table: "Event"); + } +} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index dd91c2bccc..6ff68a0b31 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -349,6 +349,12 @@ namespace Bit.MySqlMigrations.Migrations b.Property("ProviderUserId") .HasColumnType("char(36)"); + b.Property("SecretId") + .HasColumnType("char(36)"); + + b.Property("ServiceAccountId") + .HasColumnType("char(36)"); + b.Property("SystemUser") .HasColumnType("tinyint unsigned"); diff --git a/util/PostgresMigrations/Migrations/20230213133239_SecretsManagerEvent.Designer.cs b/util/PostgresMigrations/Migrations/20230213133239_SecretsManagerEvent.Designer.cs new file mode 100644 index 0000000000..e71b8e85d5 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20230213133239_SecretsManagerEvent.Designer.cs @@ -0,0 +1,2149 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20230213133239_SecretsManagerEvent")] + partial class SecretsManagerEvent + { + 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("ProductVersion", "6.0.12") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("character varying(25)"); + + b.Property("Approved") + .HasColumnType("boolean"); + + b.Property("AuthenticationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MasterPasswordHash") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequestDeviceType") + .HasColumnType("smallint"); + + b.Property("RequestFingerprint") + .HasColumnType("text"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResponseDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ResponseDeviceId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("InstallationId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.Property("SystemUser") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp with time zone"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseCustomPermissions") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseScim") + .HasColumnType("boolean"); + + b.Property("UseSecretsManager") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Config") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastSyncDate") + .HasColumnType("timestamp with time zone"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PlanSponsorshipType") + .HasColumnType("smallint"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("uuid"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("uuid"); + + b.Property("ToDelete") + .HasColumnType("boolean"); + + b.Property("ValidUntil") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("AccessSecretsManager") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BillingPhone") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("character varying(7)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("FailedLoginCount") + .HasColumnType("integer"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("KdfMemory") + .HasColumnType("integer"); + + b.Property("KdfParallelism") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LastEmailChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastFailedLoginDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKdfChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastKeyRotationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp with time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UnknownDeviceVerificationEnabled") + .HasColumnType("boolean"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("text"); + + b.Property("Read") + .HasColumnType("boolean"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Write") + .HasColumnType("boolean"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator("Discriminator").HasValue("AccessPolicy"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ClientSecret") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EncryptedPayload") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ExpireAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Scope") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Note") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("uuid"); + + b.Property("SecretsId") + .HasColumnType("uuid"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("uuid") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId"); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Connections"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20230213133239_SecretsManagerEvent.cs b/util/PostgresMigrations/Migrations/20230213133239_SecretsManagerEvent.cs new file mode 100644 index 0000000000..ff560044df --- /dev/null +++ b/util/PostgresMigrations/Migrations/20230213133239_SecretsManagerEvent.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +public partial class SecretsManagerEvent : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SecretId", + table: "Event", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "ServiceAccountId", + table: "Event", + type: "uuid", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SecretId", + table: "Event"); + + migrationBuilder.DropColumn( + name: "ServiceAccountId", + table: "Event"); + } +} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index 48a862ae72..63854e78a2 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -353,6 +353,12 @@ namespace Bit.PostgresMigrations.Migrations b.Property("ProviderUserId") .HasColumnType("uuid"); + b.Property("SecretId") + .HasColumnType("uuid"); + + b.Property("ServiceAccountId") + .HasColumnType("uuid"); + b.Property("SystemUser") .HasColumnType("smallint"); diff --git a/util/SqliteMigrations/Migrations/20230213133244_SecretsManagerEvent.Designer.cs b/util/SqliteMigrations/Migrations/20230213133244_SecretsManagerEvent.Designer.cs new file mode 100644 index 0000000000..f791006ba0 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20230213133244_SecretsManagerEvent.Designer.cs @@ -0,0 +1,2136 @@ +// +using System; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20230213133244_SecretsManagerEvent")] + partial class SecretsManagerEvent + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.12"); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCode") + .HasMaxLength(25) + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("AuthenticationDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHash") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("RequestDeviceIdentifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RequestDeviceType") + .HasColumnType("INTEGER"); + + b.Property("RequestFingerprint") + .HasColumnType("TEXT"); + + b.Property("RequestIpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ResponseDate") + .HasColumnType("TEXT"); + + b.Property("ResponseDeviceId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AuthRequest", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Attachments") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Favorites") + .HasColumnType("TEXT"); + + b.Property("Folders") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Reprompt") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("HidePasswords") + .HasColumnType("INTEGER"); + + b.Property("ReadOnly") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("GranteeId") + .HasColumnType("TEXT"); + + b.Property("GrantorId") + .HasColumnType("TEXT"); + + b.Property("KeyEncrypted") + .HasColumnType("TEXT"); + + b.Property("LastNotificationDate") + .HasColumnType("TEXT"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("WaitTimeDays") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Event", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ActingUserId") + .HasColumnType("TEXT"); + + b.Property("CipherId") + .HasColumnType("TEXT"); + + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("DeviceType") + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("InstallationId") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("PolicyId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("ProviderOrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.Property("SystemUser") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Event", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedDate") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.ToTable("Grant", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessAll") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("TEXT"); + + b.Property("OrganizationUserId") + .HasColumnType("TEXT"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.ToTable("GroupUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Installation", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Installation", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("INTEGER"); + + b.Property("MaxCollections") + .HasColumnType("INTEGER"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("TEXT"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PlanType") + .HasColumnType("INTEGER"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Seats") + .HasColumnType("INTEGER"); + + b.Property("SelfHost") + .HasColumnType("INTEGER"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("Use2fa") + .HasColumnType("INTEGER"); + + b.Property("UseApi") + .HasColumnType("INTEGER"); + + b.Property("UseCustomPermissions") + .HasColumnType("INTEGER"); + + b.Property("UseDirectory") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.Property("UseGroups") + .HasColumnType("INTEGER"); + + b.Property("UseKeyConnector") + .HasColumnType("INTEGER"); + + b.Property("UsePolicies") + .HasColumnType("INTEGER"); + + b.Property("UseResetPassword") + .HasColumnType("INTEGER"); + + b.Property("UseScim") + .HasColumnType("INTEGER"); + + b.Property("UseSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("UseSso") + .HasColumnType("INTEGER"); + + b.Property("UseTotp") + .HasColumnType("INTEGER"); + + b.Property("UsersGetPremium") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Organization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Config") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("OrganizationConnection", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("FriendlyName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("LastSyncDate") + .HasColumnType("TEXT"); + + b.Property("OfferedToEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlanSponsorshipType") + .HasColumnType("INTEGER"); + + b.Property("SponsoredOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationId") + .HasColumnType("TEXT"); + + b.Property("SponsoringOrganizationUserId") + .HasColumnType("TEXT"); + + b.Property("ToDelete") + .HasColumnType("INTEGER"); + + b.Property("ValidUntil") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SponsoredOrganizationId"); + + b.HasIndex("SponsoringOrganizationId"); + + b.ToTable("OrganizationSponsorship", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessAll") + .HasColumnType("INTEGER"); + + b.Property("AccessSecretsManager") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ResetPasswordKey") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Provider", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BillingEmail") + .HasColumnType("TEXT"); + + b.Property("BillingPhone") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress1") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress2") + .HasColumnType("TEXT"); + + b.Property("BusinessAddress3") + .HasColumnType("TEXT"); + + b.Property("BusinessCountry") + .HasColumnType("TEXT"); + + b.Property("BusinessName") + .HasColumnType("TEXT"); + + b.Property("BusinessTaxNumber") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UseEvents") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Provider", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessCount") + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("DeletionDate") + .HasColumnType("TEXT"); + + b.Property("Disabled") + .HasColumnType("INTEGER"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("HideEmail") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MaxAccessCount") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Data") + .HasColumnType("TEXT"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Rate") + .HasColumnType("TEXT"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TaxRate", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Amount") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("PaymentMethodType") + .HasColumnType("INTEGER"); + + b.Property("Refunded") + .HasColumnType("INTEGER"); + + b.Property("RefundedAmount") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccountRevisionDate") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("AvatarColor") + .HasMaxLength(7) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailVerified") + .HasColumnType("INTEGER"); + + b.Property("EquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("TEXT"); + + b.Property("FailedLoginCount") + .HasColumnType("INTEGER"); + + b.Property("ForcePasswordReset") + .HasColumnType("INTEGER"); + + b.Property("Gateway") + .HasColumnType("INTEGER"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Kdf") + .HasColumnType("INTEGER"); + + b.Property("KdfIterations") + .HasColumnType("INTEGER"); + + b.Property("KdfMemory") + .HasColumnType("INTEGER"); + + b.Property("KdfParallelism") + .HasColumnType("INTEGER"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("LastEmailChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastFailedLoginDate") + .HasColumnType("TEXT"); + + b.Property("LastKdfChangeDate") + .HasColumnType("TEXT"); + + b.Property("LastKeyRotationDate") + .HasColumnType("TEXT"); + + b.Property("LastPasswordChangeDate") + .HasColumnType("TEXT"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("TEXT"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("MaxStorageGb") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Premium") + .HasColumnType("INTEGER"); + + b.Property("PremiumExpirationDate") + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("ReferenceData") + .HasColumnType("TEXT"); + + b.Property("RenewalReminderDate") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Storage") + .HasColumnType("INTEGER"); + + b.Property("TwoFactorProviders") + .HasColumnType("TEXT"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UnknownDeviceVerificationEnabled") + .HasColumnType("INTEGER"); + + b.Property("UsesKeyConnector") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("User", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Read") + .HasColumnType("INTEGER"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Write") + .HasColumnType("INTEGER"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.ToTable("AccessPolicy", (string)null); + + b.HasDiscriminator("Discriminator").HasValue("AccessPolicy"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClientSecret") + .HasMaxLength(30) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("EncryptedPayload") + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ExpireAt") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Scope") + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("ServiceAccountId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ApiKey", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Project", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("DeletedDate") + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("DeletedDate") + .HasAnnotation("SqlServer:Clustered", false); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("Secret", (string)null); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OrganizationId") + .HasColumnType("TEXT"); + + b.Property("RevisionDate") + .HasColumnType("TEXT"); + + b.HasKey("Id") + .HasAnnotation("SqlServer:Clustered", true); + + b.HasIndex("OrganizationId") + .HasAnnotation("SqlServer:Clustered", false); + + b.ToTable("ServiceAccount", (string)null); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.Property("ProjectsId") + .HasColumnType("TEXT"); + + b.Property("SecretsId") + .HasColumnType("TEXT"); + + b.HasKey("ProjectsId", "SecretsId"); + + b.HasIndex("SecretsId"); + + b.ToTable("ProjectSecret"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("GroupId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GroupId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("GroupId"); + + b.HasDiscriminator().HasValue("group_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT") + .HasColumnName("ServiceAccountId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("ServiceAccountId"); + + b.HasDiscriminator().HasValue("service_account_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedProjectId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedProjectId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedProjectId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_project"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasBaseType("Bit.Infrastructure.EntityFramework.SecretsManager.Models.AccessPolicy"); + + b.Property("GrantedServiceAccountId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("GrantedServiceAccountId"); + + b.Property("OrganizationUserId") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("TEXT") + .HasColumnName("OrganizationUserId"); + + b.HasIndex("GrantedServiceAccountId"); + + b.HasIndex("OrganizationUserId"); + + b.HasDiscriminator().HasValue("user_service_account"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.AuthRequest", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Device", "ResponseDevice") + .WithMany() + .HasForeignKey("ResponseDeviceId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDevice"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionCipher", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionGroup", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.CollectionUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Device", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.EmergencyAccess", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Folder", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.GroupUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany("GroupUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("ApiKeys") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationConnection", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Connections") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationSponsorship", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoredOrganization") + .WithMany() + .HasForeignKey("SponsoredOrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "SponsoringOrganization") + .WithMany() + .HasForeignKey("SponsoringOrganizationId"); + + b.Navigation("SponsoredOrganization"); + + b.Navigation("SponsoringOrganization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Policy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderOrganization", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.ProviderUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Send", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoConfig", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.SsoUser", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Transaction", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ApiKey", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("ProjectSecret", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Secret", null) + .WithMany() + .HasForeignKey("SecretsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId"); + + b.Navigation("GrantedProject"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.GroupServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("GroupAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccountProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("ServiceAccountAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "ServiceAccount") + .WithMany() + .HasForeignKey("ServiceAccountId"); + + b.Navigation("GrantedProject"); + + b.Navigation("ServiceAccount"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserProjectAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", "GrantedProject") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedProjectId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedProject"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.UserServiceAccountAccessPolicy", b => + { + b.HasOne("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", "GrantedServiceAccount") + .WithMany("UserAccessPolicies") + .HasForeignKey("GrantedServiceAccountId"); + + b.HasOne("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId"); + + b.Navigation("GrantedServiceAccount"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.Organization", b => + { + b.Navigation("ApiKeys"); + + b.Navigation("Ciphers"); + + b.Navigation("Connections"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.Models.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Folders"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.Project", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("ServiceAccountAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); + + modelBuilder.Entity("Bit.Infrastructure.EntityFramework.SecretsManager.Models.ServiceAccount", b => + { + b.Navigation("GroupAccessPolicies"); + + b.Navigation("UserAccessPolicies"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/SqliteMigrations/Migrations/20230213133244_SecretsManagerEvent.cs b/util/SqliteMigrations/Migrations/20230213133244_SecretsManagerEvent.cs new file mode 100644 index 0000000000..6c61e3d455 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20230213133244_SecretsManagerEvent.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +public partial class SecretsManagerEvent : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SecretId", + table: "Event", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "ServiceAccountId", + table: "Event", + type: "TEXT", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SecretId", + table: "Event"); + + migrationBuilder.DropColumn( + name: "ServiceAccountId", + table: "Event"); + } +} diff --git a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs index e990c66d13..4fda013404 100644 --- a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -344,6 +344,12 @@ namespace Bit.SqliteMigrations.Migrations b.Property("ProviderUserId") .HasColumnType("TEXT"); + b.Property("SecretId") + .HasColumnType("TEXT"); + + b.Property("ServiceAccountId") + .HasColumnType("TEXT"); + b.Property("SystemUser") .HasColumnType("INTEGER"); From 38336dd5c476e996b82bbdb11d6d927e12d00303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?= Date: Fri, 24 Feb 2023 17:08:47 +0100 Subject: [PATCH 48/49] Add Migrator CLI workflow stub (#2741) --- .github/workflows/build-migrator-cli.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/build-migrator-cli.yml diff --git a/.github/workflows/build-migrator-cli.yml b/.github/workflows/build-migrator-cli.yml new file mode 100644 index 0000000000..29c846c0b7 --- /dev/null +++ b/.github/workflows/build-migrator-cli.yml @@ -0,0 +1,14 @@ +--- +name: Build Migrator CLI + +on: + workflow_dispatch: + +jobs: + + stub: + name: Stub + runs-on: ubuntu-22.04 + steps: + - name: Stub + run: echo "Stub" From f11c58e396756d668592329a893bdbba2a5e3bc1 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Fri, 24 Feb 2023 15:29:22 -0500 Subject: [PATCH 49/49] [SG-648] BEEEP-Refactor DuoApi class to use Httpclient (#2691) * Started work on refactoring class * Added duo api respons model * Made httpclient version of APICall * Added more properties to response model * Refactored duo api class to use httpclient * Removed unuseful comments * Fixed lint formatting --- src/Api/Controllers/TwoFactorController.cs | 5 +- .../Models/Request/TwoFactorRequestModels.cs | 3 +- .../Api/Response/Duo/DuoResponseModel.cs | 27 ++++++ src/Core/Utilities/DuoApi.cs | 91 ++++++------------- 4 files changed, 61 insertions(+), 65 deletions(-) create mode 100644 src/Core/Models/Api/Response/Duo/DuoResponseModel.cs diff --git a/src/Api/Controllers/TwoFactorController.cs b/src/Api/Controllers/TwoFactorController.cs index 07d7197d66..244d3c7387 100644 --- a/src/Api/Controllers/TwoFactorController.cs +++ b/src/Api/Controllers/TwoFactorController.cs @@ -11,7 +11,6 @@ using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Utilities; -using Bit.Core.Utilities.Duo; using Fido2NetLib; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; @@ -153,7 +152,7 @@ public class TwoFactorController : Controller try { var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host); - duoApi.JSONApiCall("GET", "/auth/v2/check"); + await duoApi.JSONApiCall("GET", "/auth/v2/check"); } catch (DuoException) { @@ -210,7 +209,7 @@ public class TwoFactorController : Controller try { var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host); - duoApi.JSONApiCall("GET", "/auth/v2/check"); + await duoApi.JSONApiCall("GET", "/auth/v2/check"); } catch (DuoException) { diff --git a/src/Api/Models/Request/TwoFactorRequestModels.cs b/src/Api/Models/Request/TwoFactorRequestModels.cs index 18ba944ea8..a2b236edfa 100644 --- a/src/Api/Models/Request/TwoFactorRequestModels.cs +++ b/src/Api/Models/Request/TwoFactorRequestModels.cs @@ -3,6 +3,7 @@ using Bit.Api.Models.Request.Accounts; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models; +using Bit.Core.Utilities; using Fido2NetLib; namespace Bit.Api.Models.Request; @@ -104,7 +105,7 @@ public class UpdateTwoFactorDuoRequestModel : SecretVerificationRequestModel, IV public override IEnumerable Validate(ValidationContext validationContext) { - if (!Core.Utilities.Duo.DuoApi.ValidHost(Host)) + if (!DuoApi.ValidHost(Host)) { yield return new ValidationResult("Host is invalid.", new string[] { nameof(Host) }); } diff --git a/src/Core/Models/Api/Response/Duo/DuoResponseModel.cs b/src/Core/Models/Api/Response/Duo/DuoResponseModel.cs new file mode 100644 index 0000000000..573d77ab0c --- /dev/null +++ b/src/Core/Models/Api/Response/Duo/DuoResponseModel.cs @@ -0,0 +1,27 @@ +using System.Text.Json.Serialization; + +namespace Bit.Core.Models.Api.Response.Duo; + +public class DuoResponseModel +{ + [JsonPropertyName("stat")] + public string Stat { get; set; } + + [JsonPropertyName("code")] + public int? Code { get; set; } + + [JsonPropertyName("message")] + public string Message { get; set; } + + [JsonPropertyName("message_detail")] + public string MessageDetail { get; set; } + + [JsonPropertyName("response")] + public Response Response { get; set; } +} + +public class Response +{ + [JsonPropertyName("time")] + public int Time { get; set; } +} diff --git a/src/Core/Utilities/DuoApi.cs b/src/Core/Utilities/DuoApi.cs index b5a3f040d4..662ca643d7 100644 --- a/src/Core/Utilities/DuoApi.cs +++ b/src/Core/Utilities/DuoApi.cs @@ -15,8 +15,9 @@ using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Web; +using Bit.Core.Models.Api.Response.Duo; -namespace Bit.Core.Utilities.Duo; +namespace Bit.Core.Utilities; public class DuoApi { @@ -27,6 +28,8 @@ public class DuoApi private readonly string _ikey; private readonly string _skey; + private readonly HttpClient _httpClient = new(); + public DuoApi(string ikey, string skey, string host) { _ikey = ikey; @@ -92,11 +95,6 @@ public class DuoApi return string.Concat("Basic ", Encode64(auth)); } - public string ApiCall(string method, string path, Dictionary parameters = null) - { - return ApiCall(method, path, parameters, 0, out var statusCode); - } - /// The request timeout, in milliseconds. /// Specify 0 to use the system-default timeout. Use caution if /// you choose to specify a custom timeout - some API @@ -104,8 +102,7 @@ public class DuoApi /// return a response until an out-of-band authentication process /// has completed. In some cases, this may take as much as a /// small number of minutes. - public string ApiCall(string method, string path, Dictionary parameters, int timeout, - out HttpStatusCode statusCode) + private async Task<(string result, HttpStatusCode statusCode)> ApiCall(string method, string path, Dictionary parameters, int timeout) { if (parameters == null) { @@ -121,58 +118,39 @@ public class DuoApi query = "?" + canonParams; } } - var url = string.Format("{0}://{1}{2}{3}", UrlScheme, _host, path, query); + var url = $"{UrlScheme}://{_host}{path}{query}"; var dateString = RFC822UtcNow(); var auth = Sign(method, path, canonParams, dateString); - var request = (HttpWebRequest)WebRequest.Create(url); - request.Method = method; - request.Accept = "application/json"; + var request = new HttpRequestMessage + { + Method = new HttpMethod(method), + RequestUri = new Uri(url), + }; request.Headers.Add("Authorization", auth); request.Headers.Add("X-Duo-Date", dateString); - request.UserAgent = UserAgent; + request.Headers.UserAgent.ParseAdd(UserAgent); + + if (timeout > 0) + { + _httpClient.Timeout = TimeSpan.FromMilliseconds(timeout); + } if (method.Equals("POST") || method.Equals("PUT")) { - var data = Encoding.UTF8.GetBytes(canonParams); - request.ContentType = "application/x-www-form-urlencoded"; - request.ContentLength = data.Length; - using (var requestStream = request.GetRequestStream()) - { - requestStream.Write(data, 0, data.Length); - } - } - if (timeout > 0) - { - request.Timeout = timeout; + request.Content = new StringContent(canonParams, Encoding.UTF8, "application/x-www-form-urlencoded"); } - // Do the request and process the result. - HttpWebResponse response; - try - { - response = (HttpWebResponse)request.GetResponse(); - } - catch (WebException ex) - { - response = (HttpWebResponse)ex.Response; - if (response == null) - { - throw; - } - } - using (var reader = new StreamReader(response.GetResponseStream())) - { - statusCode = response.StatusCode; - return reader.ReadToEnd(); - } + var response = await _httpClient.SendAsync(request); + var result = await response.Content.ReadAsStringAsync(); + var statusCode = response.StatusCode; + return (result, statusCode); } - public T JSONApiCall(string method, string path, Dictionary parameters = null) - where T : class + public async Task JSONApiCall(string method, string path, Dictionary parameters = null) { - return JSONApiCall(method, path, parameters, 0); + return await JSONApiCall(method, path, parameters, 0); } /// The request timeout, in milliseconds. @@ -182,27 +160,18 @@ public class DuoApi /// return a response until an out-of-band authentication process /// has completed. In some cases, this may take as much as a /// small number of minutes. - public T JSONApiCall(string method, string path, Dictionary parameters, int timeout) - where T : class + private async Task JSONApiCall(string method, string path, Dictionary parameters, int timeout) { - var res = ApiCall(method, path, parameters, timeout, out var statusCode); + var (res, statusCode) = await ApiCall(method, path, parameters, timeout); try { - // TODO: We should deserialize this into our own DTO and not work on dictionaries. - var dict = JsonSerializer.Deserialize>(res); - if (dict["stat"].ToString() == "OK") + var obj = JsonSerializer.Deserialize(res); + if (obj.Stat == "OK") { - return JsonSerializer.Deserialize(dict["response"].ToString()); + return obj.Response; } - var check = ToNullableInt(dict["code"].ToString()); - var code = check.GetValueOrDefault(0); - var messageDetail = string.Empty; - if (dict.ContainsKey("message_detail")) - { - messageDetail = dict["message_detail"].ToString(); - } - throw new ApiException(code, (int)statusCode, dict["message"].ToString(), messageDetail); + throw new ApiException(obj.Code ?? 0, (int)statusCode, obj.Message, obj.MessageDetail); } catch (ApiException) {