From f5a8cf5c9c7fda007b9f6b4b36b139dc348fe696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Tom=C3=A9?= <108268980+r-tome@users.noreply.github.com> Date: Fri, 14 Apr 2023 11:13:16 +0100 Subject: [PATCH] [AC-1081] Merge feature/billing-obfuscation (#2665) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [EC-1014] Create Organization Status (Pending/Created) (#2610) * [EC-427] Add columns 'Type' and 'BillingPhone' to Provider table * [EC-427] Provider table Type and BillingPhone MySql migrations * [EC-427] Provider table Type and BillingPhone Postgres migrations * [EC-427] Add mysql migration script * [EC-427] Add mysql migration script * [EC-427] Updated Provider sql script to include default column value * [EC-427] Removed default value from Provider.Type column * [EC-427] Changed migration script to include a default value constraint instead of updating the null type * [EC-427] Updated Sql project Provider table script * [EC-427] Changed migration script to use 'Create OR Alter' for views and sprocs * [EC-427] Added default values for 'BillingPhone' and 'Type' fields on sprocs [dbo].[Provider_Create] and [dbo].[Provider_Update] * [EC-427] Adjusting metadata in migration script * [EC-427] Updated Provider sprocs SQL script files * [EC-427] Fixed migration script * [EC-427] Added sqlite migration * [EC-427] Add missing Provider_Update sproc default value * [EC-427] Added missing GO action to migration script * [EC-428] Redirect to Edit after creating Provider * Revert "[EC-428] Redirect to Edit after creating Provider" This reverts commit 6347bca1ed85681710379dbffc09e25b99b93ae6. * [EC-1014] Create OrganizationStatusType and add Status column to Organizations table * [EC-1014] Added EF migrations * [EC-1014] dotnet format * [EC-1014] Changed Organization.Status from SMALLINT to TINYINT * [EC-1014] Set Organization.Status default value = 1 * [EC-1014] Setting Organization.Status default value as 1 * [EC-459 / EC-428] Admin panel: Add Provider Type to list and creation flow (#2593) * [EC-427] Add columns 'Type' and 'BillingPhone' to Provider table * [EC-427] Provider table Type and BillingPhone MySql migrations * [EC-427] Provider table Type and BillingPhone Postgres migrations * [EC-427] Add mysql migration script * [EC-427] Add mysql migration script * [EC-427] Updated Provider sql script to include default column value * [EC-427] Removed default value from Provider.Type column * [EC-427] Changed migration script to include a default value constraint instead of updating the null type * [EC-427] Updated Sql project Provider table script * [EC-427] Changed migration script to use 'Create OR Alter' for views and sprocs * [EC-427] Added default values for 'BillingPhone' and 'Type' fields on sprocs [dbo].[Provider_Create] and [dbo].[Provider_Update] * [EC-427] Adjusting metadata in migration script * [EC-427] Updated Provider sprocs SQL script files * [EC-427] Fixed migration script * [EC-427] Added sqlite migration * [EC-427] Add missing Provider_Update sproc default value * [EC-427] Added missing GO action to migration script * [EC-459] Added Type column to Providers list * [EC-428] Added Type, BusinessName and BillingEmail to CreateProviderModel * [EC-428] Updated Create Provider view to include new fields * [EC-428] Updated ProviderService to not create a ProviderUser for the type Reseller * [EC-428] Added custom validation for Provider fields depending on selected Type * [EC-428] Redirect to Edit after creating Provider * [EC-428] Setting Provider status as Created for Resellers * [EC-428] Redirect on Provider creation depending if self host server * [EC-428] Split ProviderService.CreateAsync into two methods: CreateMspAsync and CreateResellerAsync * [EC-428] Created ICreateProviderCommand and added service for injection on Admin.Startup * [EC-428] Modified Provider views to use DisplayName attribute values * [EC-428] Moved ICreateProviderCommand to Core project * [EC-428] Adding ICreateProviderCommand injection next to IProviderService * [EC-428] Moved CreateProviderCommand to Commercial.Core project * [EC-459] Added Type column to Providers list * [EC-428] Added Type, BusinessName and BillingEmail to CreateProviderModel * [EC-428] Updated Create Provider view to include new fields * [EC-428] Updated ProviderService to not create a ProviderUser for the type Reseller * [EC-428] Added custom validation for Provider fields depending on selected Type * [EC-428] Redirect to Edit after creating Provider * [EC-428] Setting Provider status as Created for Resellers * [EC-428] Redirect on Provider creation depending if self host server * [EC-428] Split ProviderService.CreateAsync into two methods: CreateMspAsync and CreateResellerAsync * [EC-428] Created ICreateProviderCommand and added service for injection on Admin.Startup * [EC-428] Modified Provider views to use DisplayName attribute values * [EC-428] Moved ICreateProviderCommand to Core project * [EC-428] Adding ICreateProviderCommand injection next to IProviderService * [EC-428] Moved CreateProviderCommand to Commercial.Core project * [EC-428] Moved CreateProviderCommand to namespace Bit.Commercial.Core.Providers * [EC-429] Provider details screen updated with Type, BillingPhone and Organization details (#2666) * [EC-430] Admin portal: Update organization information screen (#2672) * [EC-430] Added ProviderOrganizationProviderDetailsView to get Provider details for an Organization * [EC-430] Added Provider information to Organization Edit/View on Admin panel * [EC-430] Remove "Add to Reseller" button * [EC-430] Removed unused property OrganizationEditModel.ClientOwnerEmail * [EC-430] Replaced IProviderOrganizationRepository.GetProviderDetailsByOrganizationAsync with IProviderRepository.GetByOrganizationIdAsync * [EC-430] Deleted ProviderOrganizationProviderDetails and ProviderOrganizationProviderDetailsReadByOrganizationIdQuery * [EC-429] Only show Create/Add Existing Organization buttons for Reseller providers (#2723) * [EC-432] Add existing Organizations to Provider (#2683) * [EC-432] Added ProviderOrganizationUnassignedOrganizationDetails_Search stored procedure * [EC-432] Added IProviderOrganizationRepository.SearchAsync * [EC-432] Created controller ProviderOrganizationsController to assign Organizations to a Provider * [EC-432] Filter existing organizations by plans Enterprise or Team * [EC-432] Existing Organization name links to edit page * [EC-432] EF filtering out existing organizations by plan type enterprise or teams * [EC-432] Creating multiple ProviderOrganization records * [EC-432] Added ProviderOrganizationUnassignedOrganizationDetails_Search stored procedure * [EC-432] Added IProviderOrganizationRepository.SearchAsync * [EC-432] Created controller ProviderOrganizationsController to assign Organizations to a Provider * [EC-432] Filter existing organizations by plans Enterprise or Team * [EC-432] Existing Organization name links to edit page * [EC-432] EF filtering out existing organizations by plan type enterprise or teams * [EC-432] Creating multiple ProviderOrganization records * [EC-432] Renamed migration script and added missing sproc * [EC-432] Saving multiple events for the created ProviderOrganizations * [EC-432] Included unit testing for ProviderService.AddOrganizations and EventService.LogProviderOrganizationEventsAsync * [EC-432] Removed async from NoopEventService.LogProviderOrganizationEventsAsync * [EC-432] Remove unused dependency setup in ProviderServiceTests.AddOrganizations_Success * [EC-432] Renamed AddOrganizations to AddOrganizationsToReseller and removed addingUserId and key arguments * [EC-432] Added DisplayName attributes to ProviderOrganizationViewModel and used them in the view * [EC-432] Reverted changes to input fields * [EC-432] Moved unassigned organizations search to Organizations repo * [EC-432] Moved AddExistingOrganization action to ProvidersController * [EC-432] dotnet format * [EC-432] Fixed unit test issues * [EC-432] Removed unnecessary Html.DisplayNameFor for labels * [EC-432] Renamed OrganizationSearchViewModel to OrganizationUnassignedToProviderSearchViewModel * [EC-432] Modified IEventService.LogProviderOrganizationEventsAsync to receive an IEnumerable as parameter * [EC-432] Updated IProviderOrganizationRepository and replaced CreateWithManyOrganizations method with CreateManyAsync * [EC-432] Deleted ProviderOrganization_CreateWithManyOrganizations * [AC-432] Simplified Organization_UnassignedToProviderSearch query * [AC-432] Removed unnecessary setup * [EC-432] Checking if stored procedure exists before creating * [EC-432] Renamed migration file to recent date * [EC-435] Admin Portal: Add new Organization creation flow UI (#2707) * [EC-435] Created _OrganizationForm partial view. Added actions for creating an Organization assigned to a provider * [EC-435] Remove logic for creating an organization * [EC-435] Created partial view _OrganizationFormScripts * [EC-435] Remove unused ReferenceEventType * [EC-435] Added TODO comment on Organization Create * [EC-435] Checking if Provider type is Reseller on creating new assigned organization * [EC-435] Setting the Organization plan type as TeamsMonthly by default when adding to a provider * [EC-435] Removing unused buttons * [EC-435] Switched hidden fields to form submit route value * [EC-435] Moved _OrganizationForm and _OrganizationFormScripts to Shared folder * [EC-435] Moved Create organization actions from OrganizationsController to ProvidersController * [EC-435] Fixing bug on saving Organization that would have BillingEmail as null * [EC-435] Added null check to Provider * [EC-435] Moved trial buttons script logic to Edit view * [AC-431] Add new organization invite process (#2737) * [EC-435] Created _OrganizationForm partial view. Added actions for creating an Organization assigned to a provider * [EC-435] Remove logic for creating an organization * [EC-435] Created partial view _OrganizationFormScripts * [EC-435] Remove unused ReferenceEventType * [EC-435] Added TODO comment on Organization Create * [EC-435] Checking if Provider type is Reseller on creating new assigned organization * [EC-435] Setting the Organization plan type as TeamsMonthly by default when adding to a provider * [EC-435] Removing unused buttons * [EC-435] Switched hidden fields to form submit route value * [EC-435] Moved _OrganizationForm and _OrganizationFormScripts to Shared folder * [EC-435] Moved Create organization actions from OrganizationsController to ProvidersController * [AC-431] Added new ReferenceEventType OrganizationCreatedByAdmin * [AC-431] Added method IOrganizationService.CreateOrganization * [AC-431] Creating new Organization with Pending status and assigning to Provider * [AC-431] Added method to IMailService to send invitation to initialize org * [AC-431] Added methods CreatePendingOrganization and InitPendingOrganization to IOrganizationService * [AC-431] Org invite includes initOrganization parameter * [AC-431] Modified existing Accept organization user action to initialize org * [AC-431] Updated ProvidersController method name * [AC-431] Created OrganizationUserInitInvitedViewModel to link to 'accept-init-organization' url * [AC-431] Added action AcceptInit to OrganizationUsersController * [AC-431] Resend owner invite * [AC-431] dotnet format * [AC-431] Removed unused parameter 'addingUserId' from IProviderService.AddOrganization * [AC-431] Removed setting manual values for CreationDate and RevisionDate * [AC-431] Updated OrganizationService.InitPendingOrganization to throw exceptions when the Organization does not meet the required criteria * [AC-431] Modified OrganizationUserInitInvitedViewModel to inherit properties from OrganizationUserInvitedViewModel * [AC-431] Removed unecessary parameter check * [AC-431] Moved method description to IOrganizationService.InitPendingOrganization * [AC-431] Moved ApplicationCacheService.UpsertOrganizationAbilityAsync and ReferenceEventService.RaiseEventAsync to OrganizationService * [AC-431] Creating collection after creating organization * [EC-435] Fixing bug on saving Organization that would have BillingEmail as null * [AC-431] Deleted OrganizationUserInitInvitedViewModel and added parameter InitOrganization to OrganizationUserInvitedViewModel.cs * [AC-431] Checking if the user has any existing SingleOrg policies before initializing an Org * [AC-431] Remove commented code * [EC-435] Added null check to Provider * [EC-435] Moved trial buttons script logic to Edit view * [AC-431] Added EncryptedString attribute to OrganizationUserAcceptInitRequestModel.CollectionName * [AC-431] Refactored plan check condition * [AC-431] Remove duplicate _applicationCacheService.UpsertOrganizationAbilityAsync call * [AC-431] Removed IMailService.SendOrganizationInitInviteEmailAsync * [AC-431] Added parameters ClaimsPrincipal and IUserService to IOrganizationService.CreatePendingOrganization * [AC-434] Hide Billing screen for Reseller clients (#2783) * [AC-434] Added ProviderType to ProfileOrganizationResponseModel * [AC-434] Migration script * [AC-434] Fixed indentation on migration script * [AC-434] Hiding sensitive subscription data if the user does not have permissions * [AC-434] Fixed missing dependency in unit test * [AC-434] Altered BillingSubscription.Amount and BillingSubscriptionUpcomingInvoice.Amount to nullable * [AC-434] Replaced CurrentContext.ManageBilling with ViewBillingHistory, ViewSubscription, EditSubscription and EditPaymentMethods * [AC-434] Reverted change on BillingSubscription.Amount and now setting Subscription.Items = null when User does not have permission * [AC-434] Added ProviderOrganizationProviderDetails_ReadByUserId * [AC-434] Added IProviderOrganizationRepository.GetManyByUserAsync * [AC-434] Added CurrentContext.GetOrganizationProviderDetails * [AC-434] Remove unneeded join Organization table * [AC-1255] Search Existing Organizations by partial Email (#2830) * [AC-1255] Added email search field input validation * [AC-1255] Reverted added email pattern * [AC-1255] Modified Organization search by Email to search using substring * [AC-1276] Displaying an Organizations pending owners if the Organization is in a Pending status (#2834) * [AC-432] Checking that an existing Organization is not assigned to any Provider before being assigned (#2840) * [AC-432] Checking if any of the selected Organizations is already assigned to a Provider * [AC-432] Changed ProviderOrganization_ReadByOrganizationIds to only get count * [AC-432] Replaced IProviderOrganizationRepository.GetCountByOrganizationIdsAsync with call to IProviderOrganizationRepository.GetByOrganizationId * [AC-432] undo new line * [AC-432] Fixed unit test * Revert "[AC-432] Replaced IProviderOrganizationRepository.GetCountByOrganizationIdsAsync with call to IProviderOrganizationRepository.GetByOrganizationId" This reverts commit ee6e095e883d933aa0d4c6beec0d4a93777ee2b9. # Conflicts: # util/Migrator/DbScripts/2023-03-22_00_ProviderAddExistingOrganizations.sql * [AC-432] Created new migration script for ProviderOrganization_ReadCountByOrganizationIds --- .../Providers/CreateProviderCommand.cs | 62 + .../Services/ProviderService.cs | 51 +- .../Utilities/ServiceCollectionExtensions.cs | 5 +- .../CreateProviderCommandTests.cs | 52 + .../Services/ProviderServiceTests.cs | 80 +- .../Controllers/OrganizationsController.cs | 29 +- src/Admin/Controllers/ProvidersController.cs | 112 +- src/Admin/Models/CreateProviderModel.cs | 51 +- src/Admin/Models/OrganizationEditModel.cs | 26 +- .../Models/OrganizationSelectableViewModel.cs | 8 + ...tionUnassignedToProviderSearchViewModel.cs | 12 + src/Admin/Models/OrganizationViewModel.cs | 12 +- src/Admin/Models/ProviderEditModel.cs | 4 + src/Admin/Views/Organizations/Edit.cshtml | 270 +-- src/Admin/Views/Organizations/View.cshtml | 5 + .../Organizations/_ProviderInformation.cshtml | 9 + .../Providers/AddExistingOrganization.cshtml | 97 + src/Admin/Views/Providers/Create.cshtml | 45 +- .../Views/Providers/CreateOrganization.cshtml | 21 + src/Admin/Views/Providers/Edit.cshtml | 8 + src/Admin/Views/Providers/Index.cshtml | 5 +- .../Views/Providers/Organizations.cshtml | 35 +- .../Views/Providers/_ProviderScripts.cshtml | 20 + .../Views/Providers/_ViewInformation.cshtml | 8 +- .../Views/Shared/_OrganizationForm.cshtml | 247 ++ .../Shared/_OrganizationFormScripts.cshtml | 35 + .../OrganizationUsersController.cs | 22 +- .../Controllers/OrganizationsController.cs | 29 +- .../ProviderOrganizationsController.cs | 4 +- .../OrganizationUserRequestModels.cs | 13 + .../OrganizationResponseModel.cs | 32 +- .../ProfileOrganizationResponseModel.cs | 3 + .../Response/SubscriptionResponseModel.cs | 2 +- src/Core/Context/CurrentContext.cs | 46 +- src/Core/Context/ICurrentContext.cs | 5 +- src/Core/Entities/Organization.cs | 1 + src/Core/Enums/OrganizationStatusType.cs | 7 + src/Core/Enums/Provider/ProviderType.cs | 6 +- src/Core/Enums/ReferenceEventType.cs | 2 + .../OrganizationUserOrganizationDetails.cs | 5 +- ...ProviderOrganizationOrganizationDetails.cs | 5 +- .../ProviderOrganizationProviderDetails.cs | 12 + .../Mail/OrganizationUserInvitedViewModel.cs | 6 +- .../Interfaces/ICreateProviderCommand.cs | 9 + .../Repositories/IOrganizationRepository.cs | 1 + .../IProviderOrganizationRepository.cs | 3 + src/Core/Repositories/IProviderRepository.cs | 1 + src/Core/Services/IEventService.cs | 1 + src/Core/Services/IMailService.cs | 4 +- src/Core/Services/IOrganizationService.cs | 16 +- src/Core/Services/IProviderService.cs | 5 +- .../Services/Implementations/EventService.cs | 36 +- .../Implementations/HandlebarsMailService.cs | 7 +- .../Implementations/OrganizationService.cs | 104 +- .../NoopImplementations/NoopEventService.cs | 7 +- .../NoopImplementations/NoopMailService.cs | 4 +- .../NoopProviderService.cs | 7 +- .../Repositories/OrganizationRepository.cs | 14 + .../ProviderOrganizationRepository.cs | 121 + .../Repositories/ProviderRepository.cs | 13 + .../Repositories/OrganizationRepository.cs | 39 + .../ProviderOrganizationRepository.cs | 41 + .../Repositories/ProviderRepository.cs | 14 + ...izationUserOrganizationDetailsViewQuery.cs | 1 + ...OrganizationCountByOrganizationIdsQuery.cs | 21 + ...rganizationDetailsReadByProviderIdQuery.cs | 3 +- .../ProviderOrganizationReadByUserIdQuery.cs | 32 + src/Notifications/NotificationsHub.cs | 4 +- .../Utilities/DisplayAttributeHelpers.cs | 21 + src/Sql/Sql.sqlproj | 4 + .../Stored Procedures/Organization_Create.sql | 9 +- ...rganization_UnassignedToProviderSearch.sql | 46 + .../Stored Procedures/Organization_Update.sql | 6 +- ...ganizationProviderDetails_ReadByUserId.sql | 21 + ...rganization_ReadCountByOrganizationIds.sql | 18 + .../Provider_ReadByOrganizationId.sql | 15 + src/Sql/dbo/Tables/Organization.sql | 1 + ...rganizationUserOrganizationDetailsView.sql | 1 + ...derOrganizationOrganizationDetailsView.sql | 3 +- .../OrganizationsControllerTests.cs | 4 +- test/Core.Test/Services/EventServiceTests.cs | 36 + .../OrganizationRepositoryTests.cs | 42 +- .../2023-01-20_00_OrganizationStatus.sql | 291 +++ ...-02-06_00_ProviderReadByOrganizationId.sql | 16 + .../2023-03-08_OrganizationProviderType.sql | 82 + ...22_00_ProviderAddExistingOrganizations.sql | 54 + ..._00_ProviderReadCountByOrganizationIds.sql | 19 + ...30120160248_OrganizationStatus.Designer.cs | 2107 ++++++++++++++++ .../20230120160248_OrganizationStatus.cs | 25 + .../DatabaseContextModelSnapshot.cs | 3 + ...30120160253_OrganizationStatus.Designer.cs | 2118 +++++++++++++++++ .../20230120160253_OrganizationStatus.cs | 25 + .../DatabaseContextModelSnapshot.cs | 3 + ...30120160257_OrganizationStatus.Designer.cs | 2105 ++++++++++++++++ .../20230120160257_OrganizationStatus.cs | 25 + .../DatabaseContextModelSnapshot.cs | 3 + 96 files changed, 8689 insertions(+), 431 deletions(-) create mode 100644 bitwarden_license/src/Commercial.Core/Providers/CreateProviderCommand.cs create mode 100644 bitwarden_license/test/Commercial.Core.Test/ProviderFeatures/CreateProviderCommandTests.cs create mode 100644 src/Admin/Models/OrganizationSelectableViewModel.cs create mode 100644 src/Admin/Models/OrganizationUnassignedToProviderSearchViewModel.cs create mode 100644 src/Admin/Views/Organizations/_ProviderInformation.cshtml create mode 100644 src/Admin/Views/Providers/AddExistingOrganization.cshtml create mode 100644 src/Admin/Views/Providers/CreateOrganization.cshtml create mode 100644 src/Admin/Views/Providers/_ProviderScripts.cshtml create mode 100644 src/Admin/Views/Shared/_OrganizationForm.cshtml create mode 100644 src/Admin/Views/Shared/_OrganizationFormScripts.cshtml create mode 100644 src/Core/Enums/OrganizationStatusType.cs create mode 100644 src/Core/Models/Data/Provider/ProviderOrganizationProviderDetails.cs create mode 100644 src/Core/Providers/Interfaces/ICreateProviderCommand.cs create mode 100644 src/Infrastructure.EntityFramework/Repositories/Queries/ProviderOrganizationCountByOrganizationIdsQuery.cs create mode 100644 src/Infrastructure.EntityFramework/Repositories/Queries/ProviderOrganizationReadByUserIdQuery.cs create mode 100644 src/SharedWeb/Utilities/DisplayAttributeHelpers.cs create mode 100644 src/Sql/dbo/Stored Procedures/Organization_UnassignedToProviderSearch.sql create mode 100644 src/Sql/dbo/Stored Procedures/ProviderOrganizationProviderDetails_ReadByUserId.sql create mode 100644 src/Sql/dbo/Stored Procedures/ProviderOrganization_ReadCountByOrganizationIds.sql create mode 100644 src/Sql/dbo/Stored Procedures/Provider_ReadByOrganizationId.sql create mode 100644 util/Migrator/DbScripts/2023-01-20_00_OrganizationStatus.sql create mode 100644 util/Migrator/DbScripts/2023-02-06_00_ProviderReadByOrganizationId.sql create mode 100644 util/Migrator/DbScripts/2023-03-08_OrganizationProviderType.sql create mode 100644 util/Migrator/DbScripts/2023-03-22_00_ProviderAddExistingOrganizations.sql create mode 100644 util/Migrator/DbScripts/2023-04-13_00_ProviderReadCountByOrganizationIds.sql create mode 100644 util/MySqlMigrations/Migrations/20230120160248_OrganizationStatus.Designer.cs create mode 100644 util/MySqlMigrations/Migrations/20230120160248_OrganizationStatus.cs create mode 100644 util/PostgresMigrations/Migrations/20230120160253_OrganizationStatus.Designer.cs create mode 100644 util/PostgresMigrations/Migrations/20230120160253_OrganizationStatus.cs create mode 100644 util/SqliteMigrations/Migrations/20230120160257_OrganizationStatus.Designer.cs create mode 100644 util/SqliteMigrations/Migrations/20230120160257_OrganizationStatus.cs diff --git a/bitwarden_license/src/Commercial.Core/Providers/CreateProviderCommand.cs b/bitwarden_license/src/Commercial.Core/Providers/CreateProviderCommand.cs new file mode 100644 index 000000000..36b6fe85c --- /dev/null +++ b/bitwarden_license/src/Commercial.Core/Providers/CreateProviderCommand.cs @@ -0,0 +1,62 @@ +using Bit.Core.Entities.Provider; +using Bit.Core.Enums.Provider; +using Bit.Core.Exceptions; +using Bit.Core.Providers.Interfaces; +using Bit.Core.Repositories; +using Bit.Core.Services; + +namespace Bit.Commercial.Core.Providers; + +public class CreateProviderCommand : ICreateProviderCommand +{ + private readonly IProviderRepository _providerRepository; + private readonly IProviderUserRepository _providerUserRepository; + private readonly IProviderService _providerService; + private readonly IUserRepository _userRepository; + + public CreateProviderCommand( + IProviderRepository providerRepository, + IProviderUserRepository providerUserRepository, + IProviderService providerService, + IUserRepository userRepository) + { + _providerRepository = providerRepository; + _providerUserRepository = providerUserRepository; + _providerService = providerService; + _userRepository = userRepository; + } + + public async Task CreateMspAsync(Provider provider, string ownerEmail) + { + var owner = await _userRepository.GetByEmailAsync(ownerEmail); + if (owner == null) + { + throw new BadRequestException("Invalid owner. Owner must be an existing Bitwarden user."); + } + + await ProviderRepositoryCreateAsync(provider, ProviderStatusType.Pending); + + var providerUser = new ProviderUser + { + ProviderId = provider.Id, + UserId = owner.Id, + Type = ProviderUserType.ProviderAdmin, + Status = ProviderUserStatusType.Confirmed, + }; + await _providerUserRepository.CreateAsync(providerUser); + await _providerService.SendProviderSetupInviteEmailAsync(provider, owner.Email); + } + + public async Task CreateResellerAsync(Provider provider) + { + await ProviderRepositoryCreateAsync(provider, ProviderStatusType.Created); + } + + private async Task ProviderRepositoryCreateAsync(Provider provider, ProviderStatusType status) + { + provider.Status = status; + provider.Enabled = true; + provider.UseEvents = true; + await _providerRepository.CreateAsync(provider); + } +} diff --git a/bitwarden_license/src/Commercial.Core/Services/ProviderService.cs b/bitwarden_license/src/Commercial.Core/Services/ProviderService.cs index cf3b68efa..6eef1deeb 100644 --- a/bitwarden_license/src/Commercial.Core/Services/ProviderService.cs +++ b/bitwarden_license/src/Commercial.Core/Services/ProviderService.cs @@ -53,33 +53,6 @@ public class ProviderService : IProviderService _currentContext = currentContext; } - public async Task CreateAsync(string ownerEmail) - { - var owner = await _userRepository.GetByEmailAsync(ownerEmail); - if (owner == null) - { - throw new BadRequestException("Invalid owner. Owner must be an existing Bitwarden user."); - } - - var provider = new Provider - { - Status = ProviderStatusType.Pending, - Enabled = true, - UseEvents = true, - }; - await _providerRepository.CreateAsync(provider); - - var providerUser = new ProviderUser - { - ProviderId = provider.Id, - UserId = owner.Id, - Type = ProviderUserType.ProviderAdmin, - Status = ProviderUserStatusType.Confirmed, - }; - await _providerUserRepository.CreateAsync(providerUser); - await SendProviderSetupInviteEmailAsync(provider, owner.Email); - } - public async Task CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key) { var owner = await _userService.GetUserByIdAsync(ownerUserId); @@ -370,7 +343,7 @@ public class ProviderService : IProviderService return result; } - public async Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key) + public async Task AddOrganization(Guid providerId, Guid organizationId, string key) { var po = await _providerOrganizationRepository.GetByOrganizationId(organizationId); if (po != null) @@ -392,6 +365,26 @@ public class ProviderService : IProviderService await _eventService.LogProviderOrganizationEventAsync(providerOrganization, EventType.ProviderOrganization_Added); } + public async Task AddOrganizationsToReseller(Guid providerId, IEnumerable organizationIds) + { + var provider = await _providerRepository.GetByIdAsync(providerId); + if (provider.Type != ProviderType.Reseller) + { + throw new BadRequestException("Provider must be of type Reseller in order to assign Organizations to it."); + } + + var existingProviderOrganizationsCount = await _providerOrganizationRepository.GetCountByOrganizationIdsAsync(organizationIds); + if (existingProviderOrganizationsCount > 0) + { + throw new BadRequestException("Organizations must not be assigned to any Provider."); + } + + var providerOrganizationsToInsert = organizationIds.Select(orgId => new ProviderOrganization { ProviderId = providerId, OrganizationId = orgId }); + var insertedProviderOrganizations = await _providerOrganizationRepository.CreateManyAsync(providerOrganizationsToInsert); + + await _eventService.LogProviderOrganizationEventsAsync(insertedProviderOrganizations.Select(ipo => (ipo, EventType.ProviderOrganization_Added, (DateTime?)null))); + } + public async Task CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup, string clientOwnerEmail, User user) { @@ -456,7 +449,7 @@ public class ProviderService : IProviderService await SendProviderSetupInviteEmailAsync(provider, owner.Email); } - private async Task SendProviderSetupInviteEmailAsync(Provider provider, string ownerEmail) + public async Task SendProviderSetupInviteEmailAsync(Provider provider, string ownerEmail) { var token = _dataProtector.Protect($"ProviderSetupInvite {provider.Id} {ownerEmail} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}"); await _mailService.SendProviderSetupInviteEmailAsync(provider, token, ownerEmail); diff --git a/bitwarden_license/src/Commercial.Core/Utilities/ServiceCollectionExtensions.cs b/bitwarden_license/src/Commercial.Core/Utilities/ServiceCollectionExtensions.cs index 6440f27ab..b9d21915b 100644 --- a/bitwarden_license/src/Commercial.Core/Utilities/ServiceCollectionExtensions.cs +++ b/bitwarden_license/src/Commercial.Core/Utilities/ServiceCollectionExtensions.cs @@ -1,4 +1,6 @@ -using Bit.Commercial.Core.Services; +using Bit.Commercial.Core.Providers; +using Bit.Commercial.Core.Services; +using Bit.Core.Providers.Interfaces; using Bit.Core.Services; using Microsoft.Extensions.DependencyInjection; @@ -9,5 +11,6 @@ public static class ServiceCollectionExtensions public static void AddCommercialCoreServices(this IServiceCollection services) { services.AddScoped(); + services.AddScoped(); } } diff --git a/bitwarden_license/test/Commercial.Core.Test/ProviderFeatures/CreateProviderCommandTests.cs b/bitwarden_license/test/Commercial.Core.Test/ProviderFeatures/CreateProviderCommandTests.cs new file mode 100644 index 000000000..721ed24d9 --- /dev/null +++ b/bitwarden_license/test/Commercial.Core.Test/ProviderFeatures/CreateProviderCommandTests.cs @@ -0,0 +1,52 @@ +using Bit.Commercial.Core.Providers; +using Bit.Core.Entities; +using Bit.Core.Entities.Provider; +using Bit.Core.Enums.Provider; +using Bit.Core.Exceptions; +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.Commercial.Core.Test.ProviderFeatures; + +[SutProviderCustomize] +public class CreateProviderCommandTests +{ + [Theory, BitAutoData] + public async Task CreateMspAsync_UserIdIsInvalid_Throws(Provider provider, SutProvider sutProvider) + { + provider.Type = ProviderType.Msp; + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.CreateMspAsync(provider, default)); + Assert.Contains("Invalid owner.", exception.Message); + } + + [Theory, BitAutoData] + public async Task CreateMspAsync_Success(Provider provider, User user, SutProvider sutProvider) + { + provider.Type = ProviderType.Msp; + + var userRepository = sutProvider.GetDependency(); + userRepository.GetByEmailAsync(user.Email).Returns(user); + + await sutProvider.Sut.CreateMspAsync(provider, user.Email); + + await sutProvider.GetDependency().ReceivedWithAnyArgs().CreateAsync(default); + await sutProvider.GetDependency().Received(1).SendProviderSetupInviteEmailAsync(provider, user.Email); + } + + [Theory, BitAutoData] + public async Task CreateResellerAsync_Success(Provider provider, SutProvider sutProvider) + { + provider.Type = ProviderType.Reseller; + + await sutProvider.Sut.CreateResellerAsync(provider); + + await sutProvider.GetDependency().ReceivedWithAnyArgs().CreateAsync(default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().SendProviderSetupInviteEmailAsync(default, default); + } +} diff --git a/bitwarden_license/test/Commercial.Core.Test/Services/ProviderServiceTests.cs b/bitwarden_license/test/Commercial.Core.Test/Services/ProviderServiceTests.cs index 52da69186..babfa9c07 100644 --- a/bitwarden_license/test/Commercial.Core.Test/Services/ProviderServiceTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/Services/ProviderServiceTests.cs @@ -17,6 +17,7 @@ using Microsoft.AspNetCore.DataProtection; using NSubstitute; using NSubstitute.ReturnsExtensions; using Xunit; +using Provider = Bit.Core.Entities.Provider.Provider; using ProviderUser = Bit.Core.Entities.Provider.ProviderUser; namespace Bit.Commercial.Core.Test.Services; @@ -24,26 +25,6 @@ namespace Bit.Commercial.Core.Test.Services; [SutProviderCustomize] public class ProviderServiceTests { - [Theory, BitAutoData] - public async Task CreateAsync_UserIdIsInvalid_Throws(SutProvider sutProvider) - { - var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.CreateAsync(default)); - Assert.Contains("Invalid owner.", exception.Message); - } - - [Theory, BitAutoData] - public async Task CreateAsync_Success(User user, SutProvider sutProvider) - { - var userRepository = sutProvider.GetDependency(); - userRepository.GetByEmailAsync(user.Email).Returns(user); - - await sutProvider.Sut.CreateAsync(user.Email); - - await sutProvider.GetDependency().ReceivedWithAnyArgs().CreateAsync(default); - await sutProvider.GetDependency().ReceivedWithAnyArgs().SendProviderSetupInviteEmailAsync(default, default, default); - } - [Theory, BitAutoData] public async Task CompleteSetupAsync_UserIdIsInvalid_Throws(SutProvider sutProvider) { @@ -229,6 +210,14 @@ public class ProviderServiceTests Assert.True(result.All(r => r.Item2 == "")); } + [Theory, BitAutoData] + public async Task SendProviderSetupInviteEmailAsync_Success(Provider provider, string email, SutProvider sutProvider) + { + await sutProvider.Sut.SendProviderSetupInviteEmailAsync(provider, email); + + await sutProvider.GetDependency().Received(1).SendProviderSetupInviteEmailAsync(provider, Arg.Any(), email); + } + [Theory, BitAutoData] public async Task AcceptUserAsync_UserIsInvalid_Throws(SutProvider sutProvider) { @@ -429,7 +418,7 @@ public class ProviderServiceTests [Theory, BitAutoData] public async Task AddOrganization_OrganizationAlreadyBelongsToAProvider_Throws(Provider provider, - Organization organization, ProviderOrganization po, User user, string key, + Organization organization, ProviderOrganization po, string key, SutProvider sutProvider) { po.OrganizationId = organization.Id; @@ -438,12 +427,12 @@ public class ProviderServiceTests .Returns(po); var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.AddOrganization(provider.Id, organization.Id, user.Id, key)); + () => sutProvider.Sut.AddOrganization(provider.Id, organization.Id, key)); Assert.Equal("Organization already belongs to a provider.", exception.Message); } [Theory, BitAutoData] - public async Task AddOrganization_Success(Provider provider, Organization organization, User user, string key, + public async Task AddOrganization_Success(Provider provider, Organization organization, string key, SutProvider sutProvider) { organization.PlanType = PlanType.EnterpriseAnnually; @@ -453,7 +442,7 @@ public class ProviderServiceTests providerOrganizationRepository.GetByOrganizationId(organization.Id).ReturnsNull(); sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - await sutProvider.Sut.AddOrganization(provider.Id, organization.Id, user.Id, key); + await sutProvider.Sut.AddOrganization(provider.Id, organization.Id, key); await providerOrganizationRepository.ReceivedWithAnyArgs().CreateAsync(default); await sutProvider.GetDependency() @@ -461,6 +450,49 @@ public class ProviderServiceTests EventType.ProviderOrganization_Added); } + [Theory, BitAutoData] + public async Task AddOrganizationsToReseller_WithResellerProvider_Success(Provider provider, ICollection organizations, SutProvider sutProvider) + { + provider.Type = ProviderType.Reseller; + sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); + + var providerOrganizationRepository = sutProvider.GetDependency(); + foreach (var organization in organizations) + { + organization.PlanType = PlanType.EnterpriseAnnually; + } + + var organizationIds = organizations.Select(o => o.Id).ToArray(); + + await sutProvider.Sut.AddOrganizationsToReseller(provider.Id, organizationIds); + + await providerOrganizationRepository.Received(1).CreateManyAsync(Arg.Is>(i => i.All(po => po.ProviderId == provider.Id && organizations.Any(o => o.Id == po.OrganizationId)))); + await sutProvider.GetDependency().Received(1).LogProviderOrganizationEventsAsync( + Arg.Is>(events => events.All(e => + e.Item1.ProviderId == provider.Id && organizationIds.Contains(e.Item1.OrganizationId) && e.Item2 == EventType.ProviderOrganization_Added))); + } + + [Theory, BitAutoData] + public async Task AddOrganizationsToReseller_WithMspProvider_Throws(Provider provider, ICollection organizations, SutProvider sutProvider) + { + provider.Type = ProviderType.Msp; + sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); + + var providerOrganizationRepository = sutProvider.GetDependency(); + foreach (var organization in organizations) + { + organization.PlanType = PlanType.EnterpriseAnnually; + } + + var organizationIds = organizations.Select(o => o.Id).ToArray(); + + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.AddOrganizationsToReseller(provider.Id, organizationIds)); + Assert.Contains("Provider must be of type Reseller in order to assign Organizations to it.", exception.Message); + + await providerOrganizationRepository.DidNotReceiveWithAnyArgs().CreateManyAsync(default); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().LogProviderOrganizationEventsAsync(default); + } + [Theory, BitAutoData] public async Task CreateOrganizationAsync_Success(Provider provider, OrganizationSignup organizationSignup, Organization organization, string clientOwnerEmail, User user, SutProvider sutProvider) diff --git a/src/Admin/Controllers/OrganizationsController.cs b/src/Admin/Controllers/OrganizationsController.cs index 0394a341a..9a07dd11c 100644 --- a/src/Admin/Controllers/OrganizationsController.cs +++ b/src/Admin/Controllers/OrganizationsController.cs @@ -17,6 +17,7 @@ namespace Bit.Admin.Controllers; [Authorize] public class OrganizationsController : Controller { + private readonly IOrganizationService _organizationService; private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IOrganizationConnectionRepository _organizationConnectionRepository; @@ -31,9 +32,11 @@ public class OrganizationsController : Controller private readonly GlobalSettings _globalSettings; private readonly IReferenceEventService _referenceEventService; private readonly IUserService _userService; + private readonly IProviderRepository _providerRepository; private readonly ILogger _logger; public OrganizationsController( + IOrganizationService organizationService, IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, IOrganizationConnectionRepository organizationConnectionRepository, @@ -48,8 +51,10 @@ public class OrganizationsController : Controller GlobalSettings globalSettings, IReferenceEventService referenceEventService, IUserService userService, + IProviderRepository providerRepository, ILogger logger) { + _organizationService = organizationService; _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; _organizationConnectionRepository = organizationConnectionRepository; @@ -64,6 +69,7 @@ public class OrganizationsController : Controller _globalSettings = globalSettings; _referenceEventService = referenceEventService; _userService = userService; + _providerRepository = providerRepository; _logger = logger; } @@ -103,6 +109,7 @@ public class OrganizationsController : Controller return RedirectToAction("Index"); } + var provider = await _providerRepository.GetByOrganizationIdAsync(id); var ciphers = await _cipherRepository.GetManyByOrganizationIdAsync(id); var collections = await _collectionRepository.GetManyByOrganizationIdAsync(id); IEnumerable groups = null; @@ -117,7 +124,7 @@ public class OrganizationsController : Controller } var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id); var billingSyncConnection = _globalSettings.EnableCloudCommunication ? await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(id, OrganizationConnectionType.CloudBillingSync) : null; - return View(new OrganizationViewModel(organization, billingSyncConnection, users, ciphers, collections, groups, policies)); + return View(new OrganizationViewModel(organization, provider, billingSyncConnection, users, ciphers, collections, groups, policies)); } [SelfHosted(NotSelfHostedOnly = true)] @@ -129,6 +136,7 @@ public class OrganizationsController : Controller return RedirectToAction("Index"); } + var provider = await _providerRepository.GetByOrganizationIdAsync(id); var ciphers = await _cipherRepository.GetManyByOrganizationIdAsync(id); var collections = await _collectionRepository.GetManyByOrganizationIdAsync(id); IEnumerable groups = null; @@ -144,7 +152,7 @@ public class OrganizationsController : Controller var users = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(id); var billingInfo = await _paymentService.GetBillingAsync(organization); var billingSyncConnection = _globalSettings.EnableCloudCommunication ? await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(id, OrganizationConnectionType.CloudBillingSync) : null; - return View(new OrganizationEditModel(organization, users, ciphers, collections, groups, policies, + return View(new OrganizationEditModel(organization, provider, users, ciphers, collections, groups, policies, billingInfo, billingSyncConnection, _globalSettings)); } @@ -214,4 +222,21 @@ public class OrganizationsController : Controller return RedirectToAction("Index"); } + [HttpPost] + public async Task ResendOwnerInvite(Guid id) + { + var organization = await _organizationRepository.GetByIdAsync(id); + if (organization == null) + { + return RedirectToAction("Index"); + } + + var organizationUsers = await _organizationUserRepository.GetManyByOrganizationAsync(id, OrganizationUserType.Owner); + foreach (var organizationUser in organizationUsers) + { + await _organizationService.ResendInviteAsync(id, null, organizationUser.Id, true); + } + + return Json(null); + } } diff --git a/src/Admin/Controllers/ProvidersController.cs b/src/Admin/Controllers/ProvidersController.cs index a141b9fd0..25c5a3a80 100644 --- a/src/Admin/Controllers/ProvidersController.cs +++ b/src/Admin/Controllers/ProvidersController.cs @@ -1,5 +1,7 @@ using Bit.Admin.Models; using Bit.Core.Entities.Provider; +using Bit.Core.Enums.Provider; +using Bit.Core.Providers.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; @@ -13,23 +15,42 @@ namespace Bit.Admin.Controllers; [SelfHosted(NotSelfHostedOnly = true)] public class ProvidersController : Controller { + private readonly IOrganizationRepository _organizationRepository; + private readonly IOrganizationService _organizationService; private readonly IProviderRepository _providerRepository; private readonly IProviderUserRepository _providerUserRepository; private readonly IProviderOrganizationRepository _providerOrganizationRepository; private readonly GlobalSettings _globalSettings; private readonly IApplicationCacheService _applicationCacheService; private readonly IProviderService _providerService; + private readonly IReferenceEventService _referenceEventService; + private readonly IUserService _userService; + private readonly ICreateProviderCommand _createProviderCommand; - public ProvidersController(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository, - IProviderOrganizationRepository providerOrganizationRepository, IProviderService providerService, - GlobalSettings globalSettings, IApplicationCacheService applicationCacheService) + public ProvidersController( + IOrganizationRepository organizationRepository, + IOrganizationService organizationService, + IProviderRepository providerRepository, + IProviderUserRepository providerUserRepository, + IProviderOrganizationRepository providerOrganizationRepository, + IProviderService providerService, + GlobalSettings globalSettings, + IApplicationCacheService applicationCacheService, + IReferenceEventService referenceEventService, + IUserService userService, + ICreateProviderCommand createProviderCommand) { + _organizationRepository = organizationRepository; + _organizationService = organizationService; _providerRepository = providerRepository; _providerUserRepository = providerUserRepository; _providerOrganizationRepository = providerOrganizationRepository; _providerService = providerService; _globalSettings = globalSettings; _applicationCacheService = applicationCacheService; + _referenceEventService = referenceEventService; + _userService = userService; + _createProviderCommand = createProviderCommand; } public async Task Index(string name = null, string userEmail = null, int page = 1, int count = 25) @@ -75,9 +96,18 @@ public class ProvidersController : Controller return View(model); } - await _providerService.CreateAsync(model.OwnerEmail); + var provider = model.ToProvider(); + switch (provider.Type) + { + case ProviderType.Msp: + await _createProviderCommand.CreateMspAsync(provider, model.OwnerEmail); + break; + case ProviderType.Reseller: + await _createProviderCommand.CreateResellerAsync(provider); + break; + } - return RedirectToAction("Index"); + return RedirectToAction("Edit", new { id = provider.Id }); } public async Task View(Guid id) @@ -130,4 +160,76 @@ public class ProvidersController : Controller TempData["InviteResentTo"] = ownerId; return RedirectToAction("Edit", new { id = providerId }); } + + [HttpGet] + public async Task AddExistingOrganization(Guid id, string name = null, string ownerEmail = null, int page = 1, int count = 25) + { + if (page < 1) + { + page = 1; + } + + if (count < 1) + { + count = 1; + } + + var skip = (page - 1) * count; + var unassignedOrganizations = await _organizationRepository.SearchUnassignedToProviderAsync(name, ownerEmail, skip, count); + var viewModel = new OrganizationUnassignedToProviderSearchViewModel + { + OrganizationName = string.IsNullOrWhiteSpace(name) ? null : name, + OrganizationOwnerEmail = string.IsNullOrWhiteSpace(ownerEmail) ? null : ownerEmail, + Page = page, + Count = count, + Items = unassignedOrganizations.Select(uo => new OrganizationSelectableViewModel + { + Id = uo.Id, + Name = uo.Name, + PlanType = uo.PlanType + }).ToList() + }; + + return View(viewModel); + } + + [HttpPost] + public async Task AddExistingOrganization(Guid id, OrganizationUnassignedToProviderSearchViewModel model) + { + var organizationIds = model.Items.Where(o => o.Selected).Select(o => o.Id).ToArray(); + if (organizationIds.Any()) + { + await _providerService.AddOrganizationsToReseller(id, organizationIds); + } + + return RedirectToAction("Edit", "Providers", new { id = id }); + } + + [HttpGet] + public async Task CreateOrganization(Guid providerId) + { + var provider = await _providerRepository.GetByIdAsync(providerId); + if (provider is not { Type: ProviderType.Reseller }) + { + return RedirectToAction("Index"); + } + + return View(new OrganizationEditModel(provider)); + } + + [HttpPost] + public async Task CreateOrganization(Guid providerId, OrganizationEditModel model) + { + var provider = await _providerRepository.GetByIdAsync(providerId); + if (provider is not { Type: ProviderType.Reseller }) + { + return RedirectToAction("Index"); + } + + var organization = model.CreateOrganization(provider); + await _organizationService.CreatePendingOrganization(organization, model.Owners, User, _userService, model.SalesAssistedTrialStarted); + await _providerService.AddOrganization(providerId, organization.Id, null); + + return RedirectToAction("Edit", "Providers", new { id = providerId }); + } } diff --git a/src/Admin/Models/CreateProviderModel.cs b/src/Admin/Models/CreateProviderModel.cs index 9bcbf1f75..a9a2f4ac4 100644 --- a/src/Admin/Models/CreateProviderModel.cs +++ b/src/Admin/Models/CreateProviderModel.cs @@ -1,12 +1,59 @@ using System.ComponentModel.DataAnnotations; +using Bit.Core.Entities.Provider; +using Bit.Core.Enums.Provider; +using Bit.SharedWeb.Utilities; namespace Bit.Admin.Models; -public class CreateProviderModel +public class CreateProviderModel : IValidatableObject { public CreateProviderModel() { } + [Display(Name = "Provider Type")] + public ProviderType Type { get; set; } + [Display(Name = "Owner Email")] - [Required] public string OwnerEmail { get; set; } + + [Display(Name = "Business Name")] + public string BusinessName { get; set; } + + [Display(Name = "Primary Billing Email")] + public string BillingEmail { get; set; } + + public virtual Provider ToProvider() + { + return new Provider() + { + Type = Type, + BusinessName = BusinessName, + BillingEmail = BillingEmail?.ToLowerInvariant().Trim() + }; + } + + public IEnumerable Validate(ValidationContext validationContext) + { + switch (Type) + { + case ProviderType.Msp: + if (string.IsNullOrWhiteSpace(OwnerEmail)) + { + var ownerEmailDisplayName = nameof(OwnerEmail).GetDisplayAttribute()?.GetName(); + yield return new ValidationResult($"The {ownerEmailDisplayName} field is required."); + } + break; + case ProviderType.Reseller: + if (string.IsNullOrWhiteSpace(BusinessName)) + { + var businessNameDisplayName = nameof(BusinessName).GetDisplayAttribute()?.GetName(); + yield return new ValidationResult($"The {businessNameDisplayName} field is required."); + } + if (string.IsNullOrWhiteSpace(BillingEmail)) + { + var billingEmailDisplayName = nameof(BillingEmail).GetDisplayAttribute()?.GetName(); + yield return new ValidationResult($"The {billingEmailDisplayName} field is required."); + } + break; + } + } } diff --git a/src/Admin/Models/OrganizationEditModel.cs b/src/Admin/Models/OrganizationEditModel.cs index 1d18a6062..f67ad18ab 100644 --- a/src/Admin/Models/OrganizationEditModel.cs +++ b/src/Admin/Models/OrganizationEditModel.cs @@ -1,11 +1,14 @@ using System.ComponentModel.DataAnnotations; using Bit.Core.Entities; +using Bit.Core.Entities.Provider; using Bit.Core.Enums; +using Bit.Core.Enums.Provider; using Bit.Core.Models.Business; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Settings; using Bit.Core.Utilities; using Bit.Core.Vault.Entities; +using Bit.SharedWeb.Utilities; namespace Bit.Admin.Models; @@ -13,18 +16,26 @@ public class OrganizationEditModel : OrganizationViewModel { public OrganizationEditModel() { } - public OrganizationEditModel(Organization org, IEnumerable orgUsers, + public OrganizationEditModel(Provider provider) + { + Provider = provider; + BillingEmail = provider.Type == ProviderType.Reseller ? provider.BillingEmail : string.Empty; + PlanType = Core.Enums.PlanType.TeamsMonthly; + Plan = Core.Enums.PlanType.TeamsMonthly.GetDisplayAttribute()?.GetName(); + } + + public OrganizationEditModel(Organization org, Provider provider, IEnumerable orgUsers, IEnumerable ciphers, IEnumerable collections, IEnumerable groups, IEnumerable policies, BillingInfo billingInfo, IEnumerable connections, GlobalSettings globalSettings) - : base(org, connections, orgUsers, ciphers, collections, groups, policies) + : base(org, provider, connections, orgUsers, ciphers, collections, groups, policies) { BillingInfo = billingInfo; BraintreeMerchantId = globalSettings.Braintree.MerchantId; Name = org.Name; BusinessName = org.BusinessName; - BillingEmail = org.BillingEmail; + BillingEmail = provider?.Type == ProviderType.Reseller ? provider.BillingEmail : org.BillingEmail; PlanType = org.PlanType; Plan = org.Plan; Seats = org.Seats; @@ -60,7 +71,7 @@ public class OrganizationEditModel : OrganizationViewModel public string BraintreeMerchantId { get; set; } [Required] - [Display(Name = "Name")] + [Display(Name = "Organization Name")] public string Name { get; set; } [Display(Name = "Business Name")] public string BusinessName { get; set; } @@ -124,6 +135,13 @@ public class OrganizationEditModel : OrganizationViewModel public DateTime? ExpirationDate { get; set; } public bool SalesAssistedTrialStarted { get; set; } + public Organization CreateOrganization(Provider provider) + { + BillingEmail = provider.BillingEmail; + + return ToOrganization(new Organization()); + } + public Organization ToOrganization(Organization existingOrganization) { existingOrganization.Name = Name; diff --git a/src/Admin/Models/OrganizationSelectableViewModel.cs b/src/Admin/Models/OrganizationSelectableViewModel.cs new file mode 100644 index 000000000..0daa5fda8 --- /dev/null +++ b/src/Admin/Models/OrganizationSelectableViewModel.cs @@ -0,0 +1,8 @@ +using Bit.Core.Entities; + +namespace Bit.Admin.Models; + +public class OrganizationSelectableViewModel : Organization +{ + public bool Selected { get; set; } +} diff --git a/src/Admin/Models/OrganizationUnassignedToProviderSearchViewModel.cs b/src/Admin/Models/OrganizationUnassignedToProviderSearchViewModel.cs new file mode 100644 index 000000000..73aee284c --- /dev/null +++ b/src/Admin/Models/OrganizationUnassignedToProviderSearchViewModel.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace Bit.Admin.Models; + +public class OrganizationUnassignedToProviderSearchViewModel : PagedModel +{ + [Display(Name = "Organization Name")] + public string OrganizationName { get; set; } + + [Display(Name = "Owner Email")] + public string OrganizationOwnerEmail { get; set; } +} diff --git a/src/Admin/Models/OrganizationViewModel.cs b/src/Admin/Models/OrganizationViewModel.cs index 520fa30e2..57edc49a7 100644 --- a/src/Admin/Models/OrganizationViewModel.cs +++ b/src/Admin/Models/OrganizationViewModel.cs @@ -1,4 +1,5 @@ using Bit.Core.Entities; +using Bit.Core.Entities.Provider; using Bit.Core.Enums; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Vault.Entities; @@ -9,11 +10,12 @@ public class OrganizationViewModel { public OrganizationViewModel() { } - public OrganizationViewModel(Organization org, IEnumerable connections, + public OrganizationViewModel(Organization org, Provider provider, IEnumerable connections, IEnumerable orgUsers, IEnumerable ciphers, IEnumerable collections, IEnumerable groups, IEnumerable policies) { Organization = org; + Provider = provider; Connections = connections ?? Enumerable.Empty(); HasPublicPrivateKeys = org.PublicKey != null && org.PrivateKey != null; UserInvitedCount = orgUsers.Count(u => u.Status == OrganizationUserStatusType.Invited); @@ -24,17 +26,21 @@ public class OrganizationViewModel CollectionCount = collections.Count(); GroupCount = groups?.Count() ?? 0; PolicyCount = policies?.Count() ?? 0; + var organizationUserStatus = org.Status == OrganizationStatusType.Pending + ? OrganizationUserStatusType.Invited + : OrganizationUserStatusType.Confirmed; Owners = string.Join(", ", orgUsers - .Where(u => u.Type == OrganizationUserType.Owner && u.Status == OrganizationUserStatusType.Confirmed) + .Where(u => u.Type == OrganizationUserType.Owner && u.Status == organizationUserStatus) .Select(u => u.Email)); Admins = string.Join(", ", orgUsers - .Where(u => u.Type == OrganizationUserType.Admin && u.Status == OrganizationUserStatusType.Confirmed) + .Where(u => u.Type == OrganizationUserType.Admin && u.Status == organizationUserStatus) .Select(u => u.Email)); } public Organization Organization { get; set; } + public Provider Provider { get; set; } public IEnumerable Connections { get; set; } public string Owners { get; set; } public string Admins { get; set; } diff --git a/src/Admin/Models/ProviderEditModel.cs b/src/Admin/Models/ProviderEditModel.cs index 92b2f89e9..2052bf6cf 100644 --- a/src/Admin/Models/ProviderEditModel.cs +++ b/src/Admin/Models/ProviderEditModel.cs @@ -14,10 +14,13 @@ public class ProviderEditModel : ProviderViewModel Name = provider.Name; BusinessName = provider.BusinessName; BillingEmail = provider.BillingEmail; + BillingPhone = provider.BillingPhone; } [Display(Name = "Billing Email")] public string BillingEmail { get; set; } + [Display(Name = "Billing Phone Number")] + public string BillingPhone { get; set; } [Display(Name = "Business Name")] public string BusinessName { get; set; } public string Name { get; set; } @@ -28,6 +31,7 @@ public class ProviderEditModel : ProviderViewModel existingProvider.Name = Name; existingProvider.BusinessName = BusinessName; existingProvider.BillingEmail = BillingEmail?.ToLowerInvariant()?.Trim(); + existingProvider.BillingPhone = BillingPhone?.ToLowerInvariant()?.Trim(); return existingProvider; } } diff --git a/src/Admin/Views/Organizations/Edit.cshtml b/src/Admin/Views/Organizations/Edit.cshtml index b77be0d72..d959f04a0 100644 --- a/src/Admin/Views/Organizations/Edit.cshtml +++ b/src/Admin/Views/Organizations/Edit.cshtml @@ -1,21 +1,20 @@ @model OrganizationEditModel @{ - ViewData["Title"] = "Organization: " + Model.Organization.Name; + ViewData["Title"] = (Model.Provider != null ? "Client " : string.Empty) + "Organization: " + Model.Organization.Name; } @section Scripts { + @await Html.PartialAsync("_OrganizationFormScripts") + } -

Organization @Model.Organization.Name

+

@(Model.Provider != null ? "Client " : string.Empty)Organization @Model.Organization.Name

+@if (Model.Provider != null) +{ +

Provider Relationship

+ @await Html.PartialAsync("_ProviderInformation", Model.Provider) +}

Organization Information

@await Html.PartialAsync("_ViewInformation", Model)

Billing Information

@await Html.PartialAsync("_BillingInformation", new BillingInformationModel { BillingInfo = Model.BillingInfo, OrganizationId = Model.Organization.Id }) -
- -

General

-
-
-
- - -
-
-
-
- - -
-

Business Information

-
-
-
- - -
-
-
-

Plan

-
-
-
- - -
-
-
-
- - -
-
-
-
-
-
- - -
-
-
-
- - -
-
-
-
- - -
-
-
-
-
-
- - -
-
-
-

Features

-
-
-

General

-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-

Password Manager

-
- - -
-
- - -
-
-
-

Secrets Manager

-
- - -
-
-
- -

Licensing

-
-
-
- - -
-
-
-
- - -
-
-
-

Billing

-
-
-
- - -
-
-
-
-
- - -
-
-
-
-
-
-
- -
- -
- -
-
-
-
-
-
- -
- -
- -
-
-
-
-
-
+@await Html.PartialAsync("_OrganizationForm", Model)
diff --git a/src/Admin/Views/Organizations/View.cshtml b/src/Admin/Views/Organizations/View.cshtml index 38d58e61a..77a76a021 100644 --- a/src/Admin/Views/Organizations/View.cshtml +++ b/src/Admin/Views/Organizations/View.cshtml @@ -6,6 +6,11 @@

Organization @Model.Organization.Name

+@if (Model.Provider != null) +{ +

Provider Relationship

+ @await Html.PartialAsync("_ProviderInformation", Model.Provider) +}

Information

@await Html.PartialAsync("_ViewInformation", Model) @if(GlobalSettings.SelfHosted) diff --git a/src/Admin/Views/Organizations/_ProviderInformation.cshtml b/src/Admin/Views/Organizations/_ProviderInformation.cshtml new file mode 100644 index 000000000..217f9951b --- /dev/null +++ b/src/Admin/Views/Organizations/_ProviderInformation.cshtml @@ -0,0 +1,9 @@ +@using Bit.SharedWeb.Utilities +@model Bit.Core.Entities.Provider.Provider +
+
Provider Name
+
@Model.Name
+ +
Provider Type
+
@(Model.Type.GetDisplayAttribute()?.GetName())
+
\ No newline at end of file diff --git a/src/Admin/Views/Providers/AddExistingOrganization.cshtml b/src/Admin/Views/Providers/AddExistingOrganization.cshtml new file mode 100644 index 000000000..86ad5f749 --- /dev/null +++ b/src/Admin/Views/Providers/AddExistingOrganization.cshtml @@ -0,0 +1,97 @@ +@using Bit.SharedWeb.Utilities +@using Microsoft.AspNetCore.Mvc.TagHelpers +@model OrganizationUnassignedToProviderSearchViewModel + +@{ + ViewData["Title"] = "Add Existing Organization"; + var providerId = ViewContext.RouteData.Values["id"]; +} + +

Add Existing Organization

+
+
+
+ + + + + +
+
+
+ +
+
+ + + + + + + + + + @if (!Model.Items.Any()) + { + + + + } + else + { + @for (var i = 0; i < Model.Items.Count; i++) + { + + + + + + } + } + +
AllNamePlan
No results to list.
+ @Html.HiddenFor(m => Model.Items[i].Id, new { @readonly = "readonly", autocomplete = "off" }) + @Html.CheckBoxFor(m => Model.Items[i].Selected) + @Html.ActionLink(Model.Items[i].Name, "Edit", "Organizations", new { id = Model.Items[i].Id }, new { target = "_blank" })@(Model.Items[i].PlanType.GetDisplayAttribute()?.Name ?? Model.Items[i].PlanType.ToString())
+
+
+ +
+
+ +
+
+ +
+
diff --git a/src/Admin/Views/Providers/Create.cshtml b/src/Admin/Views/Providers/Create.cshtml index 0369ca017..2263da06e 100644 --- a/src/Admin/Views/Providers/Create.cshtml +++ b/src/Admin/Views/Providers/Create.cshtml @@ -1,16 +1,55 @@ -@model CreateProviderModel +@using Bit.SharedWeb.Utilities +@model CreateProviderModel @{ ViewData["Title"] = "Create Provider"; } +@section Scripts { + +} +

Create Provider

- - + + @foreach(ProviderType providerType in Enum.GetValues(typeof(ProviderType))) + { + var providerTypeValue = (int)providerType; +
+ @Html.RadioButtonFor(m => m.Type, providerType, new { id = $"providerType-{providerTypeValue}", @class = "form-check-input", onclick=$"toggleProviderTypeInfo({providerTypeValue})" }) + @Html.LabelFor(m => m.Type, providerType.GetDisplayAttribute()?.GetName(), new { @class = "form-check-label align-middle", @for = $"providerType-{providerTypeValue}" }) +
+ @Html.LabelFor(m => m.Type, providerType.GetDisplayAttribute()?.GetDescription(), new { @class = "form-check-label small text-muted ml-3 align-top", @for = $"providerType-{providerTypeValue}" }) +
+ } +
+ +
+

MSP Info

+
+ + +
+
+ +
+

Reseller Info

+
+ + +
+
+ + +
diff --git a/src/Admin/Views/Providers/CreateOrganization.cshtml b/src/Admin/Views/Providers/CreateOrganization.cshtml new file mode 100644 index 000000000..219b6854b --- /dev/null +++ b/src/Admin/Views/Providers/CreateOrganization.cshtml @@ -0,0 +1,21 @@ +@model OrganizationEditModel +@{ + ViewData["Title"] = "Create Client Organization"; +} + +@section Scripts { + @await Html.PartialAsync("_OrganizationFormScripts") +} + +

New Client Organization

+ +@await Html.PartialAsync("_OrganizationForm", Model) +
+ +
+ + + +
+
diff --git a/src/Admin/Views/Providers/Edit.cshtml b/src/Admin/Views/Providers/Edit.cshtml index f9acfb8a4..cdf8c2ecc 100644 --- a/src/Admin/Views/Providers/Edit.cshtml +++ b/src/Admin/Views/Providers/Edit.cshtml @@ -36,6 +36,14 @@
+
+
+
+ + +
+
+
@await Html.PartialAsync("Organizations", Model)
diff --git a/src/Admin/Views/Providers/Index.cshtml b/src/Admin/Views/Providers/Index.cshtml index b1d9a7a6b..f7fedb923 100644 --- a/src/Admin/Views/Providers/Index.cshtml +++ b/src/Admin/Views/Providers/Index.cshtml @@ -1,4 +1,5 @@ -@model ProvidersModel +@using Bit.SharedWeb.Utilities +@model ProvidersModel @{ ViewData["Title"] = "Providers"; } @@ -25,6 +26,7 @@ Name + Provider Type Status Created @@ -44,6 +46,7 @@ @(provider.Name ?? "Pending") + @provider.Type.GetDisplayAttribute()?.GetShortName() @provider.Status diff --git a/src/Admin/Views/Providers/Organizations.cshtml b/src/Admin/Views/Providers/Organizations.cshtml index 67e327ca0..a6cc20a1c 100644 --- a/src/Admin/Views/Providers/Organizations.cshtml +++ b/src/Admin/Views/Providers/Organizations.cshtml @@ -1,4 +1,7 @@ @model ProviderViewModel + +@await Html.PartialAsync("_ProviderScripts") +

Provider Organizations

@@ -6,7 +9,17 @@ - + + + @@ -22,8 +35,24 @@ { + + } diff --git a/src/Admin/Views/Providers/_ProviderScripts.cshtml b/src/Admin/Views/Providers/_ProviderScripts.cshtml new file mode 100644 index 000000000..d74065d56 --- /dev/null +++ b/src/Admin/Views/Providers/_ProviderScripts.cshtml @@ -0,0 +1,20 @@ + \ No newline at end of file diff --git a/src/Admin/Views/Providers/_ViewInformation.cshtml b/src/Admin/Views/Providers/_ViewInformation.cshtml index 9a3170743..ff4a2e810 100644 --- a/src/Admin/Views/Providers/_ViewInformation.cshtml +++ b/src/Admin/Views/Providers/_ViewInformation.cshtml @@ -1,4 +1,5 @@ -@model ProviderViewModel +@using Bit.SharedWeb.Utilities +@model ProviderViewModel
Id
@Model.Provider.Id
@@ -7,7 +8,10 @@
@Model.Provider.Status
Users
-
@Model.UserCount
+
@(Model.Provider.Type == ProviderType.Reseller ? "N/A" : Model.UserCount)
+ +
Provider Type
+
@(Model.Provider.Type.GetDisplayAttribute()?.GetName())
Created
@Model.Provider.CreationDate.ToString()
diff --git a/src/Admin/Views/Shared/_OrganizationForm.cshtml b/src/Admin/Views/Shared/_OrganizationForm.cshtml new file mode 100644 index 000000000..d76c45b7d --- /dev/null +++ b/src/Admin/Views/Shared/_OrganizationForm.cshtml @@ -0,0 +1,247 @@ +@using Bit.SharedWeb.Utilities +@model OrganizationEditModel + +
+ +

General

+
+
+
+ + +
+
+
+ + @if (Model.Provider?.Type == ProviderType.Reseller) + { +
+
+
+ + @if (!string.IsNullOrWhiteSpace(Model.Owners)) + { + + } + else + { + + } + +
+
+
+ } + @if (Model.Organization != null) + { +
+ + +
+ } +

Business Information

+
+
+
+ + +
+
+
+

Plan

+
+
+
+ + @{ + var planTypes = Enum.GetValues() + .Where(p => Model.Provider == null || p is >= PlanType.TeamsMonthly and <= PlanType.EnterpriseAnnually) + .Select(e => new SelectListItem + { + Value = ((int)e).ToString(), + Text = e.GetDisplayAttribute()?.GetName() ?? e.ToString() + }) + .ToList(); + } + +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+

Features

+
+
+

General

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+

Password Manager

+
+ + +
+
+ + +
+
+
+

Secrets Manager

+
+ + +
+
+
+ +

Licensing

+
+
+
+ + +
+
+
+
+ + +
+
+
+

Billing

+
+
+
+ + @if (Model.Provider?.Type == ProviderType.Reseller) + { + + } + else + { + + } +
+
+
+
+
+ + +
+
+
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+
+ +
+ +
+ +
+
+
+
+
+ \ No newline at end of file diff --git a/src/Admin/Views/Shared/_OrganizationFormScripts.cshtml b/src/Admin/Views/Shared/_OrganizationFormScripts.cshtml new file mode 100644 index 000000000..82d5deb12 --- /dev/null +++ b/src/Admin/Views/Shared/_OrganizationFormScripts.cshtml @@ -0,0 +1,35 @@ + \ No newline at end of file diff --git a/src/Api/Controllers/OrganizationUsersController.cs b/src/Api/Controllers/OrganizationUsersController.cs index e320aca76..255efdc9e 100644 --- a/src/Api/Controllers/OrganizationUsersController.cs +++ b/src/Api/Controllers/OrganizationUsersController.cs @@ -177,6 +177,20 @@ public class OrganizationUsersController : Controller await _organizationService.ResendInviteAsync(orgGuidId, userId.Value, new Guid(id)); } + [HttpPost("{organizationUserId}/accept-init")] + public async Task AcceptInit(Guid orgId, Guid organizationUserId, [FromBody] OrganizationUserAcceptInitRequestModel model) + { + var user = await _userService.GetUserByPrincipalAsync(User); + if (user == null) + { + throw new UnauthorizedAccessException(); + } + + await _organizationService.InitPendingOrganization(user.Id, orgId, model.Keys.PublicKey, model.Keys.EncryptedPrivateKey, model.CollectionName); + await _organizationService.AcceptUserAsync(organizationUserId, user, model.Token, _userService); + await _organizationService.ConfirmUserAsync(orgId, organizationUserId, model.Key, user.Id, _userService); + } + [HttpPost("{organizationUserId}/accept")] public async Task Accept(Guid orgId, Guid organizationUserId, [FromBody] OrganizationUserAcceptRequestModel model) { @@ -188,11 +202,9 @@ public class OrganizationUsersController : Controller var masterPasswordPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword); var useMasterPasswordPolicy = masterPasswordPolicy != null && - masterPasswordPolicy.Enabled && - masterPasswordPolicy.GetDataModel().AutoEnrollEnabled; - - if (useMasterPasswordPolicy && - string.IsNullOrWhiteSpace(model.ResetPasswordKey)) + masterPasswordPolicy.Enabled && + masterPasswordPolicy.GetDataModel().AutoEnrollEnabled; + if (useMasterPasswordPolicy && string.IsNullOrWhiteSpace(model.ResetPasswordKey)) { throw new BadRequestException(string.Empty, "Master Password reset is required, but not provided."); } diff --git a/src/Api/Controllers/OrganizationsController.cs b/src/Api/Controllers/OrganizationsController.cs index 92e9fcb87..ea7330b75 100644 --- a/src/Api/Controllers/OrganizationsController.cs +++ b/src/Api/Controllers/OrganizationsController.cs @@ -28,6 +28,7 @@ public class OrganizationsController : Controller private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IPolicyRepository _policyRepository; + private readonly IProviderRepository _providerRepository; private readonly IOrganizationService _organizationService; private readonly IUserService _userService; private readonly IPaymentService _paymentService; @@ -46,6 +47,7 @@ public class OrganizationsController : Controller IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, IPolicyRepository policyRepository, + IProviderRepository providerRepository, IOrganizationService organizationService, IUserService userService, IPaymentService paymentService, @@ -63,6 +65,7 @@ public class OrganizationsController : Controller _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; _policyRepository = policyRepository; + _providerRepository = providerRepository; _organizationService = organizationService; _userService = userService; _paymentService = paymentService; @@ -101,7 +104,7 @@ public class OrganizationsController : Controller public async Task GetBilling(string id) { var orgIdGuid = new Guid(id); - if (!await _currentContext.ManageBilling(orgIdGuid)) + if (!await _currentContext.ViewBillingHistory(orgIdGuid)) { throw new NotFoundException(); } @@ -120,7 +123,7 @@ public class OrganizationsController : Controller public async Task GetSubscription(string id) { var orgIdGuid = new Guid(id); - if (!await _currentContext.ManageBilling(orgIdGuid)) + if (!await _currentContext.ViewSubscription(orgIdGuid)) { throw new NotFoundException(); } @@ -139,7 +142,9 @@ public class OrganizationsController : Controller throw new NotFoundException(); } - return new OrganizationSubscriptionResponseModel(organization, subscriptionInfo); + var hideSensitiveData = !await _currentContext.EditSubscription(orgIdGuid); + + return new OrganizationSubscriptionResponseModel(organization, subscriptionInfo, hideSensitiveData); } else { @@ -240,7 +245,7 @@ public class OrganizationsController : Controller model.BillingEmail != organization.BillingEmail); var hasRequiredPermissions = updateBilling - ? await _currentContext.ManageBilling(orgIdGuid) + ? await _currentContext.EditSubscription(orgIdGuid) : await _currentContext.OrganizationOwner(orgIdGuid); if (!hasRequiredPermissions) @@ -257,7 +262,7 @@ public class OrganizationsController : Controller public async Task PostPayment(string id, [FromBody] PaymentRequestModel model) { var orgIdGuid = new Guid(id); - if (!await _currentContext.ManageBilling(orgIdGuid)) + if (!await _currentContext.EditPaymentMethods(orgIdGuid)) { throw new NotFoundException(); } @@ -280,7 +285,7 @@ public class OrganizationsController : Controller public async Task PostUpgrade(string id, [FromBody] OrganizationUpgradeRequestModel model) { var orgIdGuid = new Guid(id); - if (!await _currentContext.ManageBilling(orgIdGuid)) + if (!await _currentContext.EditSubscription(orgIdGuid)) { throw new NotFoundException(); } @@ -294,7 +299,7 @@ public class OrganizationsController : Controller public async Task PostSubscription(string id, [FromBody] OrganizationSubscriptionUpdateRequestModel model) { var orgIdGuid = new Guid(id); - if (!await _currentContext.ManageBilling(orgIdGuid)) + if (!await _currentContext.EditSubscription(orgIdGuid)) { throw new NotFoundException(); } @@ -307,7 +312,7 @@ public class OrganizationsController : Controller public async Task PostSeat(string id, [FromBody] OrganizationSeatRequestModel model) { var orgIdGuid = new Guid(id); - if (!await _currentContext.ManageBilling(orgIdGuid)) + if (!await _currentContext.EditSubscription(orgIdGuid)) { throw new NotFoundException(); } @@ -321,7 +326,7 @@ public class OrganizationsController : Controller public async Task PostStorage(string id, [FromBody] StorageRequestModel model) { var orgIdGuid = new Guid(id); - if (!await _currentContext.ManageBilling(orgIdGuid)) + if (!await _currentContext.EditSubscription(orgIdGuid)) { throw new NotFoundException(); } @@ -335,7 +340,7 @@ public class OrganizationsController : Controller public async Task PostVerifyBank(string id, [FromBody] OrganizationVerifyBankRequestModel model) { var orgIdGuid = new Guid(id); - if (!await _currentContext.ManageBilling(orgIdGuid)) + if (!await _currentContext.EditSubscription(orgIdGuid)) { throw new NotFoundException(); } @@ -348,7 +353,7 @@ public class OrganizationsController : Controller public async Task PostCancel(string id) { var orgIdGuid = new Guid(id); - if (!await _currentContext.ManageBilling(orgIdGuid)) + if (!await _currentContext.EditSubscription(orgIdGuid)) { throw new NotFoundException(); } @@ -361,7 +366,7 @@ public class OrganizationsController : Controller public async Task PostReinstate(string id) { var orgIdGuid = new Guid(id); - if (!await _currentContext.ManageBilling(orgIdGuid)) + if (!await _currentContext.EditSubscription(orgIdGuid)) { throw new NotFoundException(); } diff --git a/src/Api/Controllers/ProviderOrganizationsController.cs b/src/Api/Controllers/ProviderOrganizationsController.cs index 222d11302..6f8283280 100644 --- a/src/Api/Controllers/ProviderOrganizationsController.cs +++ b/src/Api/Controllers/ProviderOrganizationsController.cs @@ -54,9 +54,7 @@ public class ProviderOrganizationsController : Controller throw new NotFoundException(); } - var userId = _userService.GetProperUserId(User).Value; - - await _providerService.AddOrganization(providerId, model.OrganizationId, userId, model.Key); + await _providerService.AddOrganization(providerId, model.OrganizationId, model.Key); } [HttpPost("")] diff --git a/src/Api/Models/Request/Organizations/OrganizationUserRequestModels.cs b/src/Api/Models/Request/Organizations/OrganizationUserRequestModels.cs index 9a8ab0192..a702ee7c9 100644 --- a/src/Api/Models/Request/Organizations/OrganizationUserRequestModels.cs +++ b/src/Api/Models/Request/Organizations/OrganizationUserRequestModels.cs @@ -37,6 +37,19 @@ public class OrganizationUserInviteRequestModel } } +public class OrganizationUserAcceptInitRequestModel +{ + [Required] + public string Token { get; set; } + [Required] + public string Key { get; set; } + [Required] + public OrganizationKeysRequestModel Keys { get; set; } + [EncryptedString] + [EncryptedStringLength(1000)] + public string CollectionName { get; set; } +} + public class OrganizationUserAcceptRequestModel { [Required] diff --git a/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs b/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs index 0e284ab9c..d2fdc1e0b 100644 --- a/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs +++ b/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs @@ -84,28 +84,30 @@ public class OrganizationResponseModel : ResponseModel public class OrganizationSubscriptionResponseModel : OrganizationResponseModel { - public OrganizationSubscriptionResponseModel(Organization organization, SubscriptionInfo subscription = null) - : base(organization, "organizationSubscription") + public OrganizationSubscriptionResponseModel(Organization organization) : base(organization, "organizationSubscription") { - if (subscription != null) - { - Subscription = subscription.Subscription != null ? - new BillingSubscription(subscription.Subscription) : null; - UpcomingInvoice = subscription.UpcomingInvoice != null ? - new BillingSubscriptionUpcomingInvoice(subscription.UpcomingInvoice) : null; - Expiration = DateTime.UtcNow.AddYears(1); // Not used, so just give it a value. - } - else - { - Expiration = organization.ExpirationDate; - } - + Expiration = organization.ExpirationDate; StorageName = organization.Storage.HasValue ? CoreHelpers.ReadableBytesSize(organization.Storage.Value) : null; StorageGb = organization.Storage.HasValue ? Math.Round(organization.Storage.Value / 1073741824D, 2) : 0; // 1 GB } + public OrganizationSubscriptionResponseModel(Organization organization, SubscriptionInfo subscription, bool hideSensitiveData) + : this(organization) + { + Subscription = subscription.Subscription != null ? new BillingSubscription(subscription.Subscription) : null; + UpcomingInvoice = subscription.UpcomingInvoice != null ? new BillingSubscriptionUpcomingInvoice(subscription.UpcomingInvoice) : null; + Expiration = DateTime.UtcNow.AddYears(1); // Not used, so just give it a value. + + if (hideSensitiveData) + { + BillingEmail = null; + Subscription.Items = null; + UpcomingInvoice.Amount = null; + } + } + public string StorageName { get; set; } public double? StorageGb { get; set; } public BillingSubscription Subscription { get; set; } diff --git a/src/Api/Models/Response/ProfileOrganizationResponseModel.cs b/src/Api/Models/Response/ProfileOrganizationResponseModel.cs index b8895fdc9..871e48d0a 100644 --- a/src/Api/Models/Response/ProfileOrganizationResponseModel.cs +++ b/src/Api/Models/Response/ProfileOrganizationResponseModel.cs @@ -1,4 +1,5 @@ using Bit.Core.Enums; +using Bit.Core.Enums.Provider; using Bit.Core.Models.Api; using Bit.Core.Models.Data; using Bit.Core.Models.Data.Organizations.OrganizationUsers; @@ -46,6 +47,7 @@ public class ProfileOrganizationResponseModel : ResponseModel UserId = organization.UserId?.ToString(); ProviderId = organization.ProviderId?.ToString(); ProviderName = organization.ProviderName; + ProviderType = organization.ProviderType; FamilySponsorshipFriendlyName = organization.FamilySponsorshipFriendlyName; FamilySponsorshipAvailable = FamilySponsorshipFriendlyName == null && StaticStore.GetSponsoredPlan(PlanSponsorshipType.FamiliesForEnterprise) @@ -97,6 +99,7 @@ public class ProfileOrganizationResponseModel : ResponseModel public bool HasPublicAndPrivateKeys { get; set; } public string ProviderId { get; set; } public string ProviderName { get; set; } + public ProviderType? ProviderType { get; set; } public string FamilySponsorshipFriendlyName { get; set; } public bool FamilySponsorshipAvailable { get; set; } public ProductType PlanProductType { get; set; } diff --git a/src/Api/Models/Response/SubscriptionResponseModel.cs b/src/Api/Models/Response/SubscriptionResponseModel.cs index 4888bd208..3ec1b975e 100644 --- a/src/Api/Models/Response/SubscriptionResponseModel.cs +++ b/src/Api/Models/Response/SubscriptionResponseModel.cs @@ -100,6 +100,6 @@ public class BillingSubscriptionUpcomingInvoice Date = inv.Date; } - public decimal Amount { get; set; } + public decimal? Amount { get; set; } public DateTime? Date { get; set; } } diff --git a/src/Core/Context/CurrentContext.cs b/src/Core/Context/CurrentContext.cs index 598f30bbb..512ab4431 100644 --- a/src/Core/Context/CurrentContext.cs +++ b/src/Core/Context/CurrentContext.cs @@ -13,9 +13,11 @@ namespace Bit.Core.Context; public class CurrentContext : ICurrentContext { + private readonly IProviderOrganizationRepository _providerOrganizationRepository; private readonly IProviderUserRepository _providerUserRepository; private bool _builtHttpContext; private bool _builtClaimsPrincipal; + private IEnumerable _providerOrganizationProviderDetails; private IEnumerable _providerUserOrganizations; public virtual HttpContext HttpContext { get; set; } @@ -37,8 +39,11 @@ public class CurrentContext : ICurrentContext public virtual ClientType ClientType { get; set; } public virtual Guid? ServiceAccountOrganizationId { get; set; } - public CurrentContext(IProviderUserRepository providerUserRepository) + public CurrentContext( + IProviderOrganizationRepository providerOrganizationRepository, + IProviderUserRepository providerUserRepository) { + _providerOrganizationRepository = providerOrganizationRepository; _providerUserRepository = providerUserRepository; } @@ -392,15 +397,34 @@ public class CurrentContext : ICurrentContext && (o.Permissions?.ManageResetPassword ?? false)) ?? false); } - public async Task ManageBilling(Guid orgId) + public async Task ViewSubscription(Guid orgId) { - var orgManagedByProvider = await ProviderIdForOrg(orgId) != null; + var orgManagedByMspProvider = (await GetOrganizationProviderDetails()).Any(po => po.OrganizationId == orgId && po.ProviderType == ProviderType.Msp); + + return orgManagedByMspProvider + ? await ProviderUserForOrgAsync(orgId) + : await OrganizationOwner(orgId); + } + + public async Task EditSubscription(Guid orgId) + { + var orgManagedByProvider = (await GetOrganizationProviderDetails()).Any(po => po.OrganizationId == orgId); return orgManagedByProvider ? await ProviderUserForOrgAsync(orgId) : await OrganizationOwner(orgId); } + public async Task EditPaymentMethods(Guid orgId) + { + return await EditSubscription(orgId); + } + + public async Task ViewBillingHistory(Guid orgId) + { + return await EditSubscription(orgId); + } + public bool ProviderProviderAdmin(Guid providerId) { return Providers?.Any(o => o.Id == providerId && o.Type == ProviderUserType.ProviderAdmin) ?? false; @@ -433,7 +457,7 @@ public class CurrentContext : ICurrentContext public async Task ProviderUserForOrgAsync(Guid orgId) { - return (await GetProviderOrganizations()).Any(po => po.OrganizationId == orgId); + return (await GetProviderUserOrganizations()).Any(po => po.OrganizationId == orgId); } public async Task ProviderIdForOrg(Guid orgId) @@ -443,7 +467,7 @@ public class CurrentContext : ICurrentContext return null; } - var po = (await GetProviderOrganizations()) + var po = (await GetProviderUserOrganizations()) ?.FirstOrDefault(po => po.OrganizationId == orgId); return po?.ProviderId; @@ -520,7 +544,7 @@ public class CurrentContext : ICurrentContext }; } - protected async Task> GetProviderOrganizations() + protected async Task> GetProviderUserOrganizations() { if (_providerUserOrganizations == null && UserId.HasValue) { @@ -529,4 +553,14 @@ public class CurrentContext : ICurrentContext return _providerUserOrganizations; } + + protected async Task> GetOrganizationProviderDetails() + { + if (_providerOrganizationProviderDetails == null && UserId.HasValue) + { + _providerOrganizationProviderDetails = await _providerOrganizationRepository.GetManyByUserAsync(UserId.Value); + } + + return _providerOrganizationProviderDetails; + } } diff --git a/src/Core/Context/ICurrentContext.cs b/src/Core/Context/ICurrentContext.cs index a78757d09..76a71e01a 100644 --- a/src/Core/Context/ICurrentContext.cs +++ b/src/Core/Context/ICurrentContext.cs @@ -52,7 +52,10 @@ public interface ICurrentContext Task ManageUsers(Guid orgId); Task ManageScim(Guid orgId); Task ManageResetPassword(Guid orgId); - Task ManageBilling(Guid orgId); + Task ViewSubscription(Guid orgId); + Task EditSubscription(Guid orgId); + Task EditPaymentMethods(Guid orgId); + Task ViewBillingHistory(Guid orgId); Task ProviderUserForOrgAsync(Guid orgId); bool ProviderProviderAdmin(Guid providerId); bool ProviderUser(Guid providerId); diff --git a/src/Core/Entities/Organization.cs b/src/Core/Entities/Organization.cs index d99b1308f..be07467ff 100644 --- a/src/Core/Entities/Organization.cs +++ b/src/Core/Entities/Organization.cs @@ -69,6 +69,7 @@ public class Organization : ITableObject, ISubscriber, IStorable, IStorabl public DateTime RevisionDate { get; set; } = DateTime.UtcNow; public int? MaxAutoscaleSeats { get; set; } = null; public DateTime? OwnersNotifiedOfAutoscaling { get; set; } = null; + public OrganizationStatusType Status { get; set; } public void SetNewId() { diff --git a/src/Core/Enums/OrganizationStatusType.cs b/src/Core/Enums/OrganizationStatusType.cs new file mode 100644 index 000000000..1f6fb8d39 --- /dev/null +++ b/src/Core/Enums/OrganizationStatusType.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Enums; + +public enum OrganizationStatusType : byte +{ + Pending = 0, + Created = 1 +} diff --git a/src/Core/Enums/Provider/ProviderType.cs b/src/Core/Enums/Provider/ProviderType.cs index 8cced3b58..020f39eba 100644 --- a/src/Core/Enums/Provider/ProviderType.cs +++ b/src/Core/Enums/Provider/ProviderType.cs @@ -1,7 +1,11 @@ -namespace Bit.Core.Enums.Provider; +using System.ComponentModel.DataAnnotations; + +namespace Bit.Core.Enums.Provider; public enum ProviderType : byte { + [Display(ShortName = "MSP", Name = "Managed Service Provider", Description = "Access to clients organization")] Msp = 0, + [Display(ShortName = "Reseller", Name = "Reseller", Description = "Access to clients billing")] Reseller = 1, } diff --git a/src/Core/Enums/ReferenceEventType.cs b/src/Core/Enums/ReferenceEventType.cs index 71c063c97..65f3918ff 100644 --- a/src/Core/Enums/ReferenceEventType.cs +++ b/src/Core/Enums/ReferenceEventType.cs @@ -40,6 +40,8 @@ public enum ReferenceEventType CollectionCreated, [EnumMember(Value = "organization-edited-by-admin")] OrganizationEditedByAdmin, + [EnumMember(Value = "organization-created-by-admin")] + OrganizationCreatedByAdmin, [EnumMember(Value = "sm-service-account-accessed-secret")] SmServiceAccountAccessedSecret, } diff --git a/src/Core/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs b/src/Core/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs index 32b700370..1a6197e94 100644 --- a/src/Core/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs +++ b/src/Core/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs @@ -1,4 +1,6 @@ -namespace Bit.Core.Models.Data.Organizations.OrganizationUsers; +using Bit.Core.Enums.Provider; + +namespace Bit.Core.Models.Data.Organizations.OrganizationUsers; public class OrganizationUserOrganizationDetails { @@ -36,6 +38,7 @@ public class OrganizationUserOrganizationDetails public string PrivateKey { get; set; } public Guid? ProviderId { get; set; } public string ProviderName { get; set; } + public ProviderType? ProviderType { get; set; } public string FamilySponsorshipFriendlyName { get; set; } public string SsoConfig { get; set; } public DateTime? FamilySponsorshipLastSyncDate { get; set; } diff --git a/src/Core/Models/Data/Provider/ProviderOrganizationOrganizationDetails.cs b/src/Core/Models/Data/Provider/ProviderOrganizationOrganizationDetails.cs index 923bba4af..cf4f11b00 100644 --- a/src/Core/Models/Data/Provider/ProviderOrganizationOrganizationDetails.cs +++ b/src/Core/Models/Data/Provider/ProviderOrganizationOrganizationDetails.cs @@ -1,4 +1,6 @@ -namespace Bit.Core.Models.Data; +using Bit.Core.Enums; + +namespace Bit.Core.Models.Data; public class ProviderOrganizationOrganizationDetails { @@ -13,4 +15,5 @@ public class ProviderOrganizationOrganizationDetails public int UserCount { get; set; } public int? Seats { get; set; } public string Plan { get; set; } + public OrganizationStatusType Status { get; set; } } diff --git a/src/Core/Models/Data/Provider/ProviderOrganizationProviderDetails.cs b/src/Core/Models/Data/Provider/ProviderOrganizationProviderDetails.cs new file mode 100644 index 000000000..8ae9559c8 --- /dev/null +++ b/src/Core/Models/Data/Provider/ProviderOrganizationProviderDetails.cs @@ -0,0 +1,12 @@ +using Bit.Core.Enums.Provider; + +namespace Bit.Core.Models.Data; + +public class ProviderOrganizationProviderDetails +{ + public Guid Id { get; set; } + public Guid ProviderId { get; set; } + public Guid OrganizationId { get; set; } + public string ProviderName { get; set; } + public ProviderType ProviderType { get; set; } +} diff --git a/src/Core/Models/Mail/OrganizationUserInvitedViewModel.cs b/src/Core/Models/Mail/OrganizationUserInvitedViewModel.cs index cdd550aea..99156b551 100644 --- a/src/Core/Models/Mail/OrganizationUserInvitedViewModel.cs +++ b/src/Core/Models/Mail/OrganizationUserInvitedViewModel.cs @@ -9,12 +9,14 @@ public class OrganizationUserInvitedViewModel : BaseTitleContactUsMailModel public string OrganizationNameUrlEncoded { get; set; } public string Token { get; set; } public string ExpirationDate { get; set; } + public bool InitOrganization { get; set; } public string Url => string.Format("{0}/accept-organization?organizationId={1}&" + - "organizationUserId={2}&email={3}&organizationName={4}&token={5}", + "organizationUserId={2}&email={3}&organizationName={4}&token={5}&initOrganization={6}", WebVaultUrl, OrganizationId, OrganizationUserId, Email, OrganizationNameUrlEncoded, - Token); + Token, + InitOrganization); } diff --git a/src/Core/Providers/Interfaces/ICreateProviderCommand.cs b/src/Core/Providers/Interfaces/ICreateProviderCommand.cs new file mode 100644 index 000000000..4121b1533 --- /dev/null +++ b/src/Core/Providers/Interfaces/ICreateProviderCommand.cs @@ -0,0 +1,9 @@ +using Bit.Core.Entities.Provider; + +namespace Bit.Core.Providers.Interfaces; + +public interface ICreateProviderCommand +{ + Task CreateMspAsync(Provider provider, string ownerEmail); + Task CreateResellerAsync(Provider provider); +} diff --git a/src/Core/Repositories/IOrganizationRepository.cs b/src/Core/Repositories/IOrganizationRepository.cs index e8bdc869a..14126adb0 100644 --- a/src/Core/Repositories/IOrganizationRepository.cs +++ b/src/Core/Repositories/IOrganizationRepository.cs @@ -13,4 +13,5 @@ public interface IOrganizationRepository : IRepository Task> GetManyAbilitiesAsync(); Task GetByLicenseKeyAsync(string licenseKey); Task GetSelfHostedOrganizationDetailsById(Guid id); + Task> SearchUnassignedToProviderAsync(string name, string ownerEmail, int skip, int take); } diff --git a/src/Core/Repositories/IProviderOrganizationRepository.cs b/src/Core/Repositories/IProviderOrganizationRepository.cs index b546d8d2e..8550a3d7b 100644 --- a/src/Core/Repositories/IProviderOrganizationRepository.cs +++ b/src/Core/Repositories/IProviderOrganizationRepository.cs @@ -5,6 +5,9 @@ namespace Bit.Core.Repositories; public interface IProviderOrganizationRepository : IRepository { + Task> CreateManyAsync(IEnumerable providerOrganizations); Task> GetManyDetailsByProviderAsync(Guid providerId); Task GetByOrganizationId(Guid organizationId); + Task> GetManyByUserAsync(Guid userId); + Task GetCountByOrganizationIdsAsync(IEnumerable organizationIds); } diff --git a/src/Core/Repositories/IProviderRepository.cs b/src/Core/Repositories/IProviderRepository.cs index 8d92fb6d2..8939bba96 100644 --- a/src/Core/Repositories/IProviderRepository.cs +++ b/src/Core/Repositories/IProviderRepository.cs @@ -5,6 +5,7 @@ namespace Bit.Core.Repositories; public interface IProviderRepository : IRepository { + Task GetByOrganizationIdAsync(Guid organizationId); Task> SearchAsync(string name, string userEmail, int skip, int take); Task> GetManyAbilitiesAsync(); } diff --git a/src/Core/Services/IEventService.cs b/src/Core/Services/IEventService.cs index e76d08630..2288d1f92 100644 --- a/src/Core/Services/IEventService.cs +++ b/src/Core/Services/IEventService.cs @@ -25,6 +25,7 @@ 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 LogProviderOrganizationEventsAsync(IEnumerable<(ProviderOrganization, EventType, DateTime?)> events); 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/IMailService.cs b/src/Core/Services/IMailService.cs index bac0b58b7..d873ea572 100644 --- a/src/Core/Services/IMailService.cs +++ b/src/Core/Services/IMailService.cs @@ -15,8 +15,8 @@ public interface IMailService Task SendTwoFactorEmailAsync(string email, string token); Task SendNoMasterPasswordHintEmailAsync(string email); Task SendMasterPasswordHintEmailAsync(string email, string hint); - Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token, bool isFreeOrg); - Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites, bool isFreeOrg); + Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token, bool isFreeOrg, bool initOrganization = false); + Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites, bool isFreeOrg, bool initOrganization = false); Task SendOrganizationMaxSeatLimitReachedEmailAsync(Organization organization, int maxSeatCount, IEnumerable ownerEmails); Task SendOrganizationAutoscaledEmailAsync(Organization organization, int initialSeatCount, IEnumerable ownerEmails); Task SendOrganizationAcceptedEmailAsync(Organization organization, string userIdentifier, IEnumerable adminEmails); diff --git a/src/Core/Services/IOrganizationService.cs b/src/Core/Services/IOrganizationService.cs index 8082ed0f4..81afdde47 100644 --- a/src/Core/Services/IOrganizationService.cs +++ b/src/Core/Services/IOrganizationService.cs @@ -1,4 +1,5 @@ -using Bit.Core.Entities; +using System.Security.Claims; +using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Business; using Bit.Core.Models.Data; @@ -37,9 +38,8 @@ public interface IOrganizationService Task InviteUserAsync(Guid organizationId, EventSystemUser systemUser, string email, OrganizationUserType type, bool accessAll, string externalId, IEnumerable collections, IEnumerable groups); Task>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId, IEnumerable organizationUsersId); - Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId); - Task AcceptUserAsync(Guid organizationUserId, User user, string token, - IUserService userService); + Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId, bool initOrganization = false); + Task AcceptUserAsync(Guid organizationUserId, User user, string token, IUserService userService); Task AcceptUserAsync(string orgIdentifier, User user, IUserService userService); Task ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key, Guid confirmingUserId, IUserService userService); @@ -69,5 +69,13 @@ public interface IOrganizationService Task RestoreUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser, IUserService userService); Task>> RestoreUsersAsync(Guid organizationId, IEnumerable organizationUserIds, Guid? restoringUserId, IUserService userService); + Task CreatePendingOrganization(Organization organization, string ownerEmail, ClaimsPrincipal user, IUserService userService, bool salesAssistedTrialStarted); + /// + /// Update an Organization entry by setting the public/private keys, set it as 'Enabled' and move the Status from 'Pending' to 'Created'. + /// + /// + /// This method must target a disabled Organization that has null keys and status as 'Pending'. + /// + Task InitPendingOrganization(Guid userId, Guid organizationId, string publicKey, string privateKey, string collectionName); Task ReplaceAndUpdateCacheAsync(Organization org, EventType? orgEvent = null); } diff --git a/src/Core/Services/IProviderService.cs b/src/Core/Services/IProviderService.cs index c5cf039b2..c1ac8b5a3 100644 --- a/src/Core/Services/IProviderService.cs +++ b/src/Core/Services/IProviderService.cs @@ -7,7 +7,6 @@ namespace Bit.Core.Services; public interface IProviderService { - Task CreateAsync(string ownerEmail); Task CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key); Task UpdateAsync(Provider provider, bool updateBilling = false); @@ -20,11 +19,13 @@ public interface IProviderService Task>> DeleteUsersAsync(Guid providerId, IEnumerable providerUserIds, Guid deletingUserId); - Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key); + Task AddOrganization(Guid providerId, Guid organizationId, string key); + Task AddOrganizationsToReseller(Guid providerId, IEnumerable organizationIds); Task CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup, string clientOwnerEmail, User user); Task RemoveOrganizationAsync(Guid providerId, Guid providerOrganizationId, Guid removingUserId); Task LogProviderAccessToOrganizationAsync(Guid organizationId); Task ResendProviderSetupInviteEmailAsync(Guid providerId, Guid ownerId); + Task SendProviderSetupInviteEmailAsync(Provider provider, string ownerEmail); } diff --git a/src/Core/Services/Implementations/EventService.cs b/src/Core/Services/Implementations/EventService.cs index e599c1d30..96bdfe450 100644 --- a/src/Core/Services/Implementations/EventService.cs +++ b/src/Core/Services/Implementations/EventService.cs @@ -332,26 +332,38 @@ public class EventService : IEventService public async Task LogProviderOrganizationEventAsync(ProviderOrganization providerOrganization, EventType type, DateTime? date = null) + { + await LogProviderOrganizationEventsAsync(new[] { (providerOrganization, type, date) }); + } + + public async Task LogProviderOrganizationEventsAsync(IEnumerable<(ProviderOrganization, EventType, DateTime?)> events) { var providerAbilities = await _applicationCacheService.GetProviderAbilitiesAsync(); - if (!CanUseProviderEvents(providerAbilities, providerOrganization.ProviderId)) + var eventMessages = new List(); + foreach (var (providerOrganization, type, date) in events) { - return; + if (!CanUseProviderEvents(providerAbilities, providerOrganization.ProviderId)) + { + continue; + } + + var e = new EventMessage(_currentContext) + { + ProviderId = providerOrganization.ProviderId, + ProviderOrganizationId = providerOrganization.Id, + Type = type, + ActingUserId = _currentContext?.UserId, + Date = date.GetValueOrDefault(DateTime.UtcNow) + }; + + eventMessages.Add(e); } - var e = new EventMessage(_currentContext) - { - ProviderId = providerOrganization.ProviderId, - ProviderOrganizationId = providerOrganization.Id, - Type = type, - ActingUserId = _currentContext?.UserId, - Date = date.GetValueOrDefault(DateTime.UtcNow) - }; - await _eventWriteService.CreateAsync(e); + await _eventWriteService.CreateManyAsync(eventMessages); } public async Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, - DateTime? date = null) + DateTime? date = null) { var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); if (!CanUseEvents(orgAbilities, organizationDomain.OrganizationId)) diff --git a/src/Core/Services/Implementations/HandlebarsMailService.cs b/src/Core/Services/Implementations/HandlebarsMailService.cs index 6f182310d..ac8537aef 100644 --- a/src/Core/Services/Implementations/HandlebarsMailService.cs +++ b/src/Core/Services/Implementations/HandlebarsMailService.cs @@ -203,10 +203,10 @@ public class HandlebarsMailService : IMailService await _mailDeliveryService.SendEmailAsync(message); } - public Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token, bool isFreeOrg) => - BulkSendOrganizationInviteEmailAsync(organizationName, new[] { (orgUser, token) }, isFreeOrg); + public Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token, bool isFreeOrg, bool initOrganization = false) => + BulkSendOrganizationInviteEmailAsync(organizationName, new[] { (orgUser, token) }, isFreeOrg, initOrganization); - public async Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites, bool isFreeOrg) + public async Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites, bool isFreeOrg, bool initOrganization = false) { MailQueueMessage CreateMessage(string email, object model) { @@ -229,6 +229,7 @@ public class HandlebarsMailService : IMailService OrganizationNameUrlEncoded = WebUtility.UrlEncode(organizationName), WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash, SiteName = _globalSettings.SiteName, + InitOrganization = initOrganization } )); diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index 9e7ba842c..3399e3a30 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.Security.Claims; +using System.Text.Json; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; @@ -598,7 +599,7 @@ public class OrganizationService : IOrganizationService bool provider = false) { var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == signup.Plan); - if (!(plan is { LegacyYear: null })) + if (plan is not { LegacyYear: null }) { throw new BadRequestException("Invalid plan selected."); } @@ -649,6 +650,7 @@ public class OrganizationService : IOrganizationService PrivateKey = signup.PrivateKey, CreationDate = DateTime.UtcNow, RevisionDate = DateTime.UtcNow, + Status = OrganizationStatusType.Created }; if (plan.Type == PlanType.Free && !provider) @@ -748,7 +750,8 @@ public class OrganizationService : IOrganizationService PublicKey = publicKey, PrivateKey = privateKey, CreationDate = DateTime.UtcNow, - RevisionDate = DateTime.UtcNow + RevisionDate = DateTime.UtcNow, + Status = OrganizationStatusType.Created }; var result = await SignUpAsync(organization, owner.Id, ownerKey, collectionName, false); @@ -1167,14 +1170,14 @@ public class OrganizationService : IOrganizationService continue; } - await SendInviteAsync(orgUser, org); + await SendInviteAsync(orgUser, org, false); result.Add(Tuple.Create(orgUser, "")); } return result; } - public async Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId) + public async Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId, bool initOrganization = false) { var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId); if (orgUser == null || orgUser.OrganizationId != organizationId || @@ -1184,7 +1187,7 @@ public class OrganizationService : IOrganizationService } var org = await GetOrgById(orgUser.OrganizationId); - await SendInviteAsync(orgUser, org); + await SendInviteAsync(orgUser, org, initOrganization); } private async Task SendInvitesAsync(IEnumerable orgUsers, Organization organization) @@ -1196,13 +1199,13 @@ public class OrganizationService : IOrganizationService orgUsers.Select(o => (o, new ExpiringToken(MakeToken(o), DateTime.UtcNow.AddDays(5)))), organization.PlanType == PlanType.Free); } - private async Task SendInviteAsync(OrganizationUser orgUser, Organization organization) + private async Task SendInviteAsync(OrganizationUser orgUser, Organization organization, bool initOrganization) { var now = DateTime.UtcNow; var nowMillis = CoreHelpers.ToEpocMilliseconds(now); var token = _dataProtector.Protect( $"OrganizationUserInvite {orgUser.Id} {orgUser.Email} {nowMillis}"); - await _mailService.SendOrganizationInviteEmailAsync(organization.Name, orgUser, new ExpiringToken(token, now.AddDays(5)), organization.PlanType == PlanType.Free); + await _mailService.SendOrganizationInviteEmailAsync(organization.Name, orgUser, new ExpiringToken(token, now.AddDays(5)), organization.PlanType == PlanType.Free, initOrganization); } public async Task AcceptUserAsync(Guid organizationUserId, User user, string token, @@ -2425,4 +2428,89 @@ public class OrganizationService : IOrganizationService return status; } + + public async Task CreatePendingOrganization(Organization organization, string ownerEmail, ClaimsPrincipal user, IUserService userService, bool salesAssistedTrialStarted) + { + var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType); + if (plan is not { LegacyYear: null }) + { + throw new BadRequestException("Invalid plan selected."); + } + + if (plan.Disabled) + { + throw new BadRequestException("Plan not found."); + } + + organization.Id = CoreHelpers.GenerateComb(); + organization.Enabled = false; + organization.Status = OrganizationStatusType.Pending; + + await SignUpAsync(organization, default, null, null, true); + + var ownerOrganizationUser = new OrganizationUser + { + OrganizationId = organization.Id, + UserId = null, + Email = ownerEmail, + Key = null, + Type = OrganizationUserType.Owner, + Status = OrganizationUserStatusType.Invited, + AccessAll = true + }; + await _organizationUserRepository.CreateAsync(ownerOrganizationUser); + + await SendInviteAsync(ownerOrganizationUser, organization, true); + await _eventService.LogOrganizationUserEventAsync(ownerOrganizationUser, EventType.OrganizationUser_Invited); + + await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.OrganizationCreatedByAdmin, organization) + { + EventRaisedByUser = userService.GetUserName(user), + SalesAssistedTrialStarted = salesAssistedTrialStarted, + }); + } + + public async Task InitPendingOrganization(Guid userId, Guid organizationId, string publicKey, string privateKey, string collectionName) + { + await ValidateSignUpPoliciesAsync(userId); + + var org = await GetOrgById(organizationId); + + if (org.Enabled) + { + throw new BadRequestException("Organization is already enabled."); + } + + if (org.Status != OrganizationStatusType.Pending) + { + throw new BadRequestException("Organization is not on a Pending status."); + } + + if (!string.IsNullOrEmpty(org.PublicKey)) + { + throw new BadRequestException("Organization already has a Public Key."); + } + + if (!string.IsNullOrEmpty(org.PrivateKey)) + { + throw new BadRequestException("Organization already has a Private Key."); + } + + org.Enabled = true; + org.Status = OrganizationStatusType.Created; + org.PublicKey = publicKey; + org.PrivateKey = privateKey; + + await UpdateAsync(org); + + if (!string.IsNullOrWhiteSpace(collectionName)) + { + var defaultCollection = new Collection + { + Name = collectionName, + OrganizationId = org.Id + }; + await _collectionRepository.CreateAsync(defaultCollection); + } + } } diff --git a/src/Core/Services/NoopImplementations/NoopEventService.cs b/src/Core/Services/NoopImplementations/NoopEventService.cs index c773c6a26..9eaefdab3 100644 --- a/src/Core/Services/NoopImplementations/NoopEventService.cs +++ b/src/Core/Services/NoopImplementations/NoopEventService.cs @@ -70,8 +70,13 @@ public class NoopEventService : IEventService return Task.FromResult(0); } + public Task LogProviderOrganizationEventsAsync(IEnumerable<(ProviderOrganization, EventType, DateTime?)> events) + { + return Task.FromResult(0); + } + public Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, - DateTime? date = null) + DateTime? date = null) { return Task.FromResult(0); } diff --git a/src/Core/Services/NoopImplementations/NoopMailService.cs b/src/Core/Services/NoopImplementations/NoopMailService.cs index 07dd0a39f..8f0cc2eb2 100644 --- a/src/Core/Services/NoopImplementations/NoopMailService.cs +++ b/src/Core/Services/NoopImplementations/NoopMailService.cs @@ -52,12 +52,12 @@ public class NoopMailService : IMailService return Task.FromResult(0); } - public Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token, bool isFreeOrg) + public Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token, bool isFreeOrg, bool initOrganization = false) { return Task.FromResult(0); } - public Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites, bool isFreeOrg) + public Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites, bool isFreeOrg, bool initOrganization = false) { return Task.FromResult(0); } diff --git a/src/Core/Services/NoopImplementations/NoopProviderService.cs b/src/Core/Services/NoopImplementations/NoopProviderService.cs index 478c5c6c1..596ec9171 100644 --- a/src/Core/Services/NoopImplementations/NoopProviderService.cs +++ b/src/Core/Services/NoopImplementations/NoopProviderService.cs @@ -7,8 +7,6 @@ namespace Bit.Core.Services; public class NoopProviderService : IProviderService { - public Task CreateAsync(string ownerEmail) => throw new NotImplementedException(); - public Task CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key) => throw new NotImplementedException(); public Task UpdateAsync(Provider provider, bool updateBilling = false) => throw new NotImplementedException(); @@ -25,7 +23,9 @@ public class NoopProviderService : IProviderService public Task>> DeleteUsersAsync(Guid providerId, IEnumerable providerUserIds, Guid deletingUserId) => throw new NotImplementedException(); - public Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key) => throw new NotImplementedException(); + public Task AddOrganization(Guid providerId, Guid organizationId, string key) => throw new NotImplementedException(); + + public Task AddOrganizationsToReseller(Guid providerId, IEnumerable organizationIds) => throw new NotImplementedException(); public Task CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup, string clientOwnerEmail, User user) => throw new NotImplementedException(); @@ -34,4 +34,5 @@ public class NoopProviderService : IProviderService public Task LogProviderAccessToOrganizationAsync(Guid organizationId) => throw new NotImplementedException(); public Task ResendProviderSetupInviteEmailAsync(Guid providerId, Guid userId) => throw new NotImplementedException(); + public Task SendProviderSetupInviteEmailAsync(Provider provider, string ownerEmail) => throw new NotImplementedException(); } diff --git a/src/Infrastructure.Dapper/Repositories/OrganizationRepository.cs b/src/Infrastructure.Dapper/Repositories/OrganizationRepository.cs index 52f486212..8a3f30448 100644 --- a/src/Infrastructure.Dapper/Repositories/OrganizationRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/OrganizationRepository.cs @@ -134,4 +134,18 @@ public class OrganizationRepository : Repository, IOrganizat return selfHostOrganization; } } + + public async Task> SearchUnassignedToProviderAsync(string name, string ownerEmail, int skip, int take) + { + using (var connection = new SqlConnection(ReadOnlyConnectionString)) + { + var results = await connection.QueryAsync( + "[dbo].[Organization_UnassignedToProviderSearch]", + new { Name = name, OwnerEmail = ownerEmail, Skip = skip, Take = take }, + commandType: CommandType.StoredProcedure, + commandTimeout: 120); + + return results.ToList(); + } + } } diff --git a/src/Infrastructure.Dapper/Repositories/ProviderOrganizationRepository.cs b/src/Infrastructure.Dapper/Repositories/ProviderOrganizationRepository.cs index f77a724f9..604d81a6e 100644 --- a/src/Infrastructure.Dapper/Repositories/ProviderOrganizationRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/ProviderOrganizationRepository.cs @@ -18,6 +18,48 @@ public class ProviderOrganizationRepository : Repository> CreateManyAsync(IEnumerable providerOrganizations) + { + var entities = providerOrganizations.ToList(); + + if (!entities.Any()) + { + return default; + } + + foreach (var providerOrganization in entities) + { + providerOrganization.SetNewId(); + } + + using (var connection = new SqlConnection(ConnectionString)) + { + connection.Open(); + + using (var transaction = connection.BeginTransaction()) + { + try + { + using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction)) + { + bulkCopy.DestinationTableName = "[dbo].[ProviderOrganization]"; + var dataTable = BuildProviderOrganizationsTable(bulkCopy, entities); + await bulkCopy.WriteToServerAsync(dataTable); + } + + transaction.Commit(); + + return entities.ToList(); + } + catch + { + transaction.Rollback(); + throw; + } + } + } + } + public async Task> GetManyDetailsByProviderAsync(Guid providerId) { using (var connection = new SqlConnection(ConnectionString)) @@ -43,4 +85,83 @@ public class ProviderOrganizationRepository : Repository> GetManyByUserAsync(Guid userId) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + "[dbo].[ProviderOrganizationProviderDetails_ReadByUserId]", + new { UserId = userId }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } + + public async Task GetCountByOrganizationIdsAsync( + IEnumerable organizationIds) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.ExecuteScalarAsync( + $"[{Schema}].[ProviderOrganization_ReadCountByOrganizationIds]", + new { Ids = organizationIds.ToGuidIdArrayTVP() }, + commandType: CommandType.StoredProcedure); + + return results; + } + } + + private DataTable BuildProviderOrganizationsTable(SqlBulkCopy bulkCopy, IEnumerable providerOrganizations) + { + var po = providerOrganizations.FirstOrDefault(); + if (po == null) + { + throw new ApplicationException("Must have some ProviderOrganizations to bulk import."); + } + + var providerOrganizationsTable = new DataTable("ProviderOrganizationDataTable"); + + var idColumn = new DataColumn(nameof(po.Id), typeof(Guid)); + providerOrganizationsTable.Columns.Add(idColumn); + var providerIdColumn = new DataColumn(nameof(po.ProviderId), typeof(Guid)); + providerOrganizationsTable.Columns.Add(providerIdColumn); + var organizationIdColumn = new DataColumn(nameof(po.OrganizationId), typeof(Guid)); + providerOrganizationsTable.Columns.Add(organizationIdColumn); + var keyColumn = new DataColumn(nameof(po.Key), typeof(string)); + providerOrganizationsTable.Columns.Add(keyColumn); + var settingsColumn = new DataColumn(nameof(po.Settings), typeof(string)); + providerOrganizationsTable.Columns.Add(settingsColumn); + var creationDateColumn = new DataColumn(nameof(po.CreationDate), po.CreationDate.GetType()); + providerOrganizationsTable.Columns.Add(creationDateColumn); + var revisionDateColumn = new DataColumn(nameof(po.RevisionDate), po.RevisionDate.GetType()); + providerOrganizationsTable.Columns.Add(revisionDateColumn); + + foreach (DataColumn col in providerOrganizationsTable.Columns) + { + bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName); + } + + var keys = new DataColumn[1]; + keys[0] = idColumn; + providerOrganizationsTable.PrimaryKey = keys; + + foreach (var providerOrganization in providerOrganizations) + { + var row = providerOrganizationsTable.NewRow(); + + row[idColumn] = providerOrganization.Id; + row[providerIdColumn] = providerOrganization.ProviderId; + row[organizationIdColumn] = providerOrganization.OrganizationId; + row[keyColumn] = providerOrganization.Key; + row[settingsColumn] = providerOrganization.Settings; + row[creationDateColumn] = providerOrganization.CreationDate; + row[revisionDateColumn] = providerOrganization.RevisionDate; + + providerOrganizationsTable.Rows.Add(row); + } + + return providerOrganizationsTable; + } } diff --git a/src/Infrastructure.Dapper/Repositories/ProviderRepository.cs b/src/Infrastructure.Dapper/Repositories/ProviderRepository.cs index 6a7f82b34..39bc02931 100644 --- a/src/Infrastructure.Dapper/Repositories/ProviderRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/ProviderRepository.cs @@ -18,6 +18,19 @@ public class ProviderRepository : Repository, IProviderRepositor : base(connectionString, readOnlyConnectionString) { } + public async Task GetByOrganizationIdAsync(Guid organizationId) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + "[dbo].[Provider_ReadByOrganizationId]", + new { OrganizationId = organizationId }, + commandType: CommandType.StoredProcedure); + + return results.FirstOrDefault(); + } + } + public async Task> SearchAsync(string name, string userEmail, int skip, int take) { using (var connection = new SqlConnection(ReadOnlyConnectionString)) diff --git a/src/Infrastructure.EntityFramework/Repositories/OrganizationRepository.cs b/src/Infrastructure.EntityFramework/Repositories/OrganizationRepository.cs index 7f0028d25..c353b1366 100644 --- a/src/Infrastructure.EntityFramework/Repositories/OrganizationRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/OrganizationRepository.cs @@ -91,6 +91,45 @@ public class OrganizationRepository : Repository> SearchUnassignedToProviderAsync(string name, string ownerEmail, int skip, int take) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from o in dbContext.Organizations + where o.PlanType >= PlanType.TeamsMonthly && o.PlanType <= PlanType.EnterpriseAnnually && + !dbContext.ProviderOrganizations.Any(po => po.OrganizationId == o.Id) && + (string.IsNullOrWhiteSpace(name) || EF.Functions.Like(o.Name, $"%{name}%")) + select o; + + if (!string.IsNullOrWhiteSpace(ownerEmail)) + { + if (dbContext.Database.IsNpgsql()) + { + query = from o in query + join ou in dbContext.OrganizationUsers + on o.Id equals ou.OrganizationId + join u in dbContext.Users + on ou.UserId equals u.Id + where ou.Type == OrganizationUserType.Owner && EF.Functions.ILike(EF.Functions.Collate(u.Email, "default"), $"{ownerEmail}%") + select o; + } + else + { + query = from o in query + join ou in dbContext.OrganizationUsers + on o.Id equals ou.OrganizationId + join u in dbContext.Users + on ou.UserId equals u.Id + where ou.Type == OrganizationUserType.Owner && EF.Functions.Like(u.Email, $"{ownerEmail}%") + select o; + } + } + + return await query.OrderByDescending(o => o.CreationDate).Skip(skip).Take(take).ToArrayAsync(); + } + } + public async Task UpdateStorageAsync(Guid id) { await OrganizationUpdateStorage(id); diff --git a/src/Infrastructure.EntityFramework/Repositories/ProviderOrganizationRepository.cs b/src/Infrastructure.EntityFramework/Repositories/ProviderOrganizationRepository.cs index 5d17d38bb..c5a6d34e2 100644 --- a/src/Infrastructure.EntityFramework/Repositories/ProviderOrganizationRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/ProviderOrganizationRepository.cs @@ -15,6 +15,30 @@ public class ProviderOrganizationRepository : : base(serviceScopeFactory, mapper, context => context.ProviderOrganizations) { } + public async Task> CreateManyAsync(IEnumerable providerOrganizations) + { + var entities = providerOrganizations.ToList(); + + if (!entities.Any()) + { + return default; + } + + foreach (var providerOrganization in entities) + { + providerOrganization.SetNewId(); + } + + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + await dbContext.AddRangeAsync(entities); + await dbContext.SaveChangesAsync(); + } + + return entities; + } + public async Task> GetManyDetailsByProviderAsync(Guid providerId) { using (var scope = ServiceScopeFactory.CreateScope()) @@ -32,4 +56,21 @@ public class ProviderOrganizationRepository : var dbContext = GetDatabaseContext(scope); return await GetDbSet(dbContext).Where(po => po.OrganizationId == organizationId).FirstOrDefaultAsync(); } + + public async Task> GetManyByUserAsync(Guid userId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = new ProviderOrganizationReadByUserIdQuery(userId); + var data = await query.Run(dbContext).ToListAsync(); + return data; + } + } + + public async Task GetCountByOrganizationIdsAsync(IEnumerable organizationIds) + { + var query = new ProviderOrganizationCountByOrganizationIdsQuery(organizationIds); + return await GetCountFromQuery(query); + } } diff --git a/src/Infrastructure.EntityFramework/Repositories/ProviderRepository.cs b/src/Infrastructure.EntityFramework/Repositories/ProviderRepository.cs index 62c4d79b8..8f6610073 100644 --- a/src/Infrastructure.EntityFramework/Repositories/ProviderRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/ProviderRepository.cs @@ -25,6 +25,20 @@ public class ProviderRepository : Repository, I await base.DeleteAsync(provider); } + public async Task GetByOrganizationIdAsync(Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var query = from p in dbContext.Providers + join po in dbContext.ProviderOrganizations + on p.Id equals po.ProviderId + where po.OrganizationId == organizationId + select p; + return await query.FirstOrDefaultAsync(); + } + } + public async Task> SearchAsync(string name, string userEmail, int skip, int take) { using (var scope = ServiceScopeFactory.CreateScope()) diff --git a/src/Infrastructure.EntityFramework/Repositories/Queries/OrganizationUserOrganizationDetailsViewQuery.cs b/src/Infrastructure.EntityFramework/Repositories/Queries/OrganizationUserOrganizationDetailsViewQuery.cs index aecad397b..1a400a948 100644 --- a/src/Infrastructure.EntityFramework/Repositories/Queries/OrganizationUserOrganizationDetailsViewQuery.cs +++ b/src/Infrastructure.EntityFramework/Repositories/Queries/OrganizationUserOrganizationDetailsViewQuery.cs @@ -55,6 +55,7 @@ public class OrganizationUserOrganizationDetailsViewQuery : IQuery +{ + private readonly IEnumerable _organizationIds; + + public ProviderOrganizationCountByOrganizationIdsQuery(IEnumerable organizationIds) + { + _organizationIds = organizationIds; + } + + public IQueryable Run(DatabaseContext dbContext) + { + var query = from po in dbContext.ProviderOrganizations + where _organizationIds.Contains(po.OrganizationId) + select po; + return query; + } +} diff --git a/src/Infrastructure.EntityFramework/Repositories/Queries/ProviderOrganizationOrganizationDetailsReadByProviderIdQuery.cs b/src/Infrastructure.EntityFramework/Repositories/Queries/ProviderOrganizationOrganizationDetailsReadByProviderIdQuery.cs index 1429a136c..04d690a74 100644 --- a/src/Infrastructure.EntityFramework/Repositories/Queries/ProviderOrganizationOrganizationDetailsReadByProviderIdQuery.cs +++ b/src/Infrastructure.EntityFramework/Repositories/Queries/ProviderOrganizationOrganizationDetailsReadByProviderIdQuery.cs @@ -31,7 +31,8 @@ public class ProviderOrganizationOrganizationDetailsReadByProviderIdQuery : IQue RevisionDate = x.po.RevisionDate, UserCount = x.o.OrganizationUsers.Count(ou => ou.Status == Core.Enums.OrganizationUserStatusType.Confirmed), Seats = x.o.Seats, - Plan = x.o.Plan + Plan = x.o.Plan, + Status = x.o.Status }); } } diff --git a/src/Infrastructure.EntityFramework/Repositories/Queries/ProviderOrganizationReadByUserIdQuery.cs b/src/Infrastructure.EntityFramework/Repositories/Queries/ProviderOrganizationReadByUserIdQuery.cs new file mode 100644 index 000000000..d28e178ab --- /dev/null +++ b/src/Infrastructure.EntityFramework/Repositories/Queries/ProviderOrganizationReadByUserIdQuery.cs @@ -0,0 +1,32 @@ +using Bit.Core.Models.Data; + +namespace Bit.Infrastructure.EntityFramework.Repositories.Queries; + +public class ProviderOrganizationReadByUserIdQuery : IQuery +{ + private readonly Guid _userId; + + public ProviderOrganizationReadByUserIdQuery(Guid userId) + { + _userId = userId; + } + + public IQueryable Run(DatabaseContext dbContext) + { + var query = from po in dbContext.ProviderOrganizations + join ou in dbContext.OrganizationUsers + on po.OrganizationId equals ou.OrganizationId + join p in dbContext.Providers + on po.ProviderId equals p.Id + where ou.UserId == _userId + select new ProviderOrganizationProviderDetails + { + Id = po.Id, + OrganizationId = po.OrganizationId, + ProviderId = po.ProviderId, + ProviderName = p.Name, + ProviderType = p.Type + }; + return query; + } +} diff --git a/src/Notifications/NotificationsHub.cs b/src/Notifications/NotificationsHub.cs index 6d7a66b89..a86cf329c 100644 --- a/src/Notifications/NotificationsHub.cs +++ b/src/Notifications/NotificationsHub.cs @@ -18,7 +18,7 @@ public class NotificationsHub : Microsoft.AspNetCore.SignalR.Hub public override async Task OnConnectedAsync() { - var currentContext = new CurrentContext(null); + var currentContext = new CurrentContext(null, null); await currentContext.BuildAsync(Context.User, _globalSettings); if (currentContext.Organizations != null) { @@ -33,7 +33,7 @@ public class NotificationsHub : Microsoft.AspNetCore.SignalR.Hub public override async Task OnDisconnectedAsync(Exception exception) { - var currentContext = new CurrentContext(null); + var currentContext = new CurrentContext(null, null); await currentContext.BuildAsync(Context.User, _globalSettings); if (currentContext.Organizations != null) { diff --git a/src/SharedWeb/Utilities/DisplayAttributeHelpers.cs b/src/SharedWeb/Utilities/DisplayAttributeHelpers.cs new file mode 100644 index 000000000..4a48a2d16 --- /dev/null +++ b/src/SharedWeb/Utilities/DisplayAttributeHelpers.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using System.Reflection; + +namespace Bit.SharedWeb.Utilities; + +public static class DisplayAttributeHelpers +{ + public static DisplayAttribute GetDisplayAttribute(this Enum enumValue) + { + return enumValue.GetType() + .GetMember(enumValue.ToString()) + .First() + .GetCustomAttribute(); + } + + public static DisplayAttribute GetDisplayAttribute(this string property) + { + MemberInfo propertyInfo = typeof(T).GetProperty(property); + return propertyInfo?.GetCustomAttribute(typeof(DisplayAttribute)) as DisplayAttribute; + } +} diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 76f639f87..e90d236a8 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -263,6 +263,10 @@ + + + + diff --git a/src/Sql/dbo/Stored Procedures/Organization_Create.sql b/src/Sql/dbo/Stored Procedures/Organization_Create.sql index 2cf67d10d..f16492260 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_Create.sql @@ -43,7 +43,8 @@ @UseKeyConnector BIT = 0, @UseScim BIT = 0, @UseCustomPermissions BIT = 0, - @UseSecretsManager BIT = 0 + @UseSecretsManager BIT = 0, + @Status TINYINT = 0 AS BEGIN SET NOCOUNT ON @@ -94,7 +95,8 @@ BEGIN [UseKeyConnector], [UseScim], [UseCustomPermissions], - [UseSecretsManager] + [UseSecretsManager], + [Status] ) VALUES ( @@ -142,6 +144,7 @@ BEGIN @UseKeyConnector, @UseScim, @UseCustomPermissions, - @UseSecretsManager + @UseSecretsManager, + @Status ) END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Organization_UnassignedToProviderSearch.sql b/src/Sql/dbo/Stored Procedures/Organization_UnassignedToProviderSearch.sql new file mode 100644 index 000000000..45fcbaeb9 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Organization_UnassignedToProviderSearch.sql @@ -0,0 +1,46 @@ +CREATE PROCEDURE [dbo].[Organization_UnassignedToProviderSearch] + @Name NVARCHAR(50), + @OwnerEmail NVARCHAR(256), + @Skip INT = 0, + @Take INT = 25 +WITH RECOMPILE +AS +BEGIN + SET NOCOUNT ON + DECLARE @NameLikeSearch NVARCHAR(55) = '%' + @Name + '%' + DECLARE @OwnerLikeSearch NVARCHAR(55) = @OwnerEmail + '%' + + IF @OwnerEmail IS NOT NULL + BEGIN + SELECT + O.* + FROM + [dbo].[OrganizationView] O + INNER JOIN + [dbo].[OrganizationUser] OU ON O.[Id] = OU.[OrganizationId] + INNER JOIN + [dbo].[User] U ON U.[Id] = OU.[UserId] + WHERE + O.[PlanType] >= 8 AND O.[PlanType] <= 11 -- Get 'Team' and 'Enterprise' Organizations + AND NOT EXISTS (SELECT * FROM [dbo].[ProviderOrganizationView] PO WHERE PO.[OrganizationId] = O.[Id]) + AND (@Name IS NULL OR O.[Name] LIKE @NameLikeSearch) + AND (U.[Email] LIKE @OwnerLikeSearch) + ORDER BY O.[CreationDate] DESC + OFFSET @Skip ROWS + FETCH NEXT @Take ROWS ONLY + END + ELSE + BEGIN + SELECT + O.* + FROM + [dbo].[OrganizationView] O + WHERE + O.[PlanType] >= 8 AND O.[PlanType] <= 11 -- Get 'Team' and 'Enterprise' Organizations + AND NOT EXISTS (SELECT * FROM [dbo].[ProviderOrganizationView] PO WHERE PO.[OrganizationId] = O.[Id]) + AND (@Name IS NULL OR O.[Name] LIKE @NameLikeSearch) + ORDER BY O.[CreationDate] DESC + OFFSET @Skip ROWS + FETCH NEXT @Take ROWS ONLY + END +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Organization_Update.sql b/src/Sql/dbo/Stored Procedures/Organization_Update.sql index 2b754d1cb..caa502932 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_Update.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_Update.sql @@ -43,7 +43,8 @@ @UseKeyConnector BIT = 0, @UseScim BIT = 0, @UseCustomPermissions BIT = 0, - @UseSecretsManager BIT = 0 + @UseSecretsManager BIT = 0, + @Status TINYINT = 0 AS BEGIN SET NOCOUNT ON @@ -94,7 +95,8 @@ BEGIN [UseKeyConnector] = @UseKeyConnector, [UseScim] = @UseScim, [UseCustomPermissions] = @UseCustomPermissions, - [UseSecretsManager] = @UseSecretsManager + [UseSecretsManager] = @UseSecretsManager, + [Status] = @Status WHERE [Id] = @Id END diff --git a/src/Sql/dbo/Stored Procedures/ProviderOrganizationProviderDetails_ReadByUserId.sql b/src/Sql/dbo/Stored Procedures/ProviderOrganizationProviderDetails_ReadByUserId.sql new file mode 100644 index 000000000..5441e6dce --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/ProviderOrganizationProviderDetails_ReadByUserId.sql @@ -0,0 +1,21 @@ +CREATE PROCEDURE [dbo].[ProviderOrganizationProviderDetails_ReadByUserId] + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + +SELECT + PO.Id, + PO.OrganizationId, + PO.ProviderId, + P.Name as ProviderName, + P.[Type] as ProviderType +FROM + [dbo].[ProviderOrganizationView] PO +INNER JOIN + [dbo].[OrganizationUser] OU ON PO.OrganizationId = OU.OrganizationId +INNER JOIN + [dbo].[Provider] P ON PO.ProviderId = P.Id +WHERE + OU.UserId = @UserId +END diff --git a/src/Sql/dbo/Stored Procedures/ProviderOrganization_ReadCountByOrganizationIds.sql b/src/Sql/dbo/Stored Procedures/ProviderOrganization_ReadCountByOrganizationIds.sql new file mode 100644 index 000000000..43ee0dcce --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/ProviderOrganization_ReadCountByOrganizationIds.sql @@ -0,0 +1,18 @@ +CREATE PROCEDURE [dbo].[ProviderOrganization_ReadCountByOrganizationIds] + @Ids AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + IF (SELECT COUNT(1) FROM @Ids) < 1 + BEGIN + RETURN(-1) + END + + SELECT + COUNT(1) + FROM + [dbo].[ProviderOrganizationView] + WHERE + [OrganizationId] IN (SELECT [Id] FROM @Ids) +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Provider_ReadByOrganizationId.sql b/src/Sql/dbo/Stored Procedures/Provider_ReadByOrganizationId.sql new file mode 100644 index 000000000..9f775f9b2 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Provider_ReadByOrganizationId.sql @@ -0,0 +1,15 @@ +CREATE PROCEDURE [dbo].[Provider_ReadByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + P.* + FROM + [dbo].[ProviderView] P + INNER JOIN + [dbo].[ProviderOrganization] PO ON PO.[ProviderId] = P.[Id] + WHERE + PO.[OrganizationId] = @OrganizationId +END diff --git a/src/Sql/dbo/Tables/Organization.sql b/src/Sql/dbo/Tables/Organization.sql index 87b1c2ff2..2d87d4de4 100644 --- a/src/Sql/dbo/Tables/Organization.sql +++ b/src/Sql/dbo/Tables/Organization.sql @@ -44,6 +44,7 @@ [UseScim] BIT NOT NULL CONSTRAINT [DF_Organization_UseScim] DEFAULT (0), [UseCustomPermissions] BIT NOT NULL CONSTRAINT [DF_Organization_UseCustomPermissions] DEFAULT (0), [UseSecretsManager] BIT NOT NULL CONSTRAINT [DF_Organization_UseSecretsManager] DEFAULT (0), + [Status] TINYINT NOT NULL CONSTRAINT [DF_Organization_Status] DEFAULT (1), CONSTRAINT [PK_Organization] PRIMARY KEY CLUSTERED ([Id] ASC) ); diff --git a/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql b/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql index d475ca216..3a7bc10ed 100644 --- a/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql +++ b/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql @@ -35,6 +35,7 @@ SELECT OU.[Permissions], PO.[ProviderId], P.[Name] ProviderName, + P.[Type] ProviderType, SS.[Data] SsoConfig, OS.[FriendlyName] FamilySponsorshipFriendlyName, OS.[LastSyncDate] FamilySponsorshipLastSyncDate, diff --git a/src/Sql/dbo/Views/ProviderOrganizationOrganizationDetailsView.sql b/src/Sql/dbo/Views/ProviderOrganizationOrganizationDetailsView.sql index 21ce0d2da..5e5bce9eb 100644 --- a/src/Sql/dbo/Views/ProviderOrganizationOrganizationDetailsView.sql +++ b/src/Sql/dbo/Views/ProviderOrganizationOrganizationDetailsView.sql @@ -11,7 +11,8 @@ SELECT PO.[RevisionDate], (SELECT COUNT(1) FROM [dbo].[OrganizationUser] OU WHERE OU.OrganizationId = PO.OrganizationId AND OU.Status = 2) UserCount, O.[Seats], - O.[Plan] + O.[Plan], + O.[Status] FROM [dbo].[ProviderOrganization] PO LEFT JOIN diff --git a/test/Api.Test/Controllers/OrganizationsControllerTests.cs b/test/Api.Test/Controllers/OrganizationsControllerTests.cs index 0933a6ae5..a3d8192f3 100644 --- a/test/Api.Test/Controllers/OrganizationsControllerTests.cs +++ b/test/Api.Test/Controllers/OrganizationsControllerTests.cs @@ -24,6 +24,7 @@ public class OrganizationsControllerTests : IDisposable private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IPaymentService _paymentService; private readonly IPolicyRepository _policyRepository; + private readonly IProviderRepository _providerRepository; private readonly ISsoConfigRepository _ssoConfigRepository; private readonly ISsoConfigService _ssoConfigService; private readonly IUserService _userService; @@ -46,6 +47,7 @@ public class OrganizationsControllerTests : IDisposable _organizationUserRepository = Substitute.For(); _paymentService = Substitute.For(); _policyRepository = Substitute.For(); + _providerRepository = Substitute.For(); _ssoConfigRepository = Substitute.For(); _ssoConfigService = Substitute.For(); _getOrganizationApiKeyQuery = Substitute.For(); @@ -57,7 +59,7 @@ public class OrganizationsControllerTests : IDisposable _updateOrganizationLicenseCommand = Substitute.For(); _sut = new OrganizationsController(_organizationRepository, _organizationUserRepository, - _policyRepository, _organizationService, _userService, _paymentService, _currentContext, + _policyRepository, _providerRepository, _organizationService, _userService, _paymentService, _currentContext, _ssoConfigRepository, _ssoConfigService, _getOrganizationApiKeyQuery, _rotateOrganizationApiKeyCommand, _createOrganizationApiKeyCommand, _organizationApiKeyRepository, _updateOrganizationLicenseCommand, _cloudGetOrganizationLicenseQuery, _globalSettings); diff --git a/test/Core.Test/Services/EventServiceTests.cs b/test/Core.Test/Services/EventServiceTests.cs index 8454f05f5..2cda759b1 100644 --- a/test/Core.Test/Services/EventServiceTests.cs +++ b/test/Core.Test/Services/EventServiceTests.cs @@ -214,4 +214,40 @@ public class EventServiceTests await sutProvider.GetDependency().Received(1).CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(expected, new[] { "IdempotencyId" }))); } + + [Theory, BitAutoData] + public async Task LogProviderOrganizationEventsAsync_LogsRequiredInfo(Provider provider, ICollection providerOrganizations, EventType eventType, DateTime date, + Guid actingUserId, Guid providerId, string ipAddress, DeviceType deviceType, SutProvider sutProvider) + { + foreach (var providerOrganization in providerOrganizations) + { + providerOrganization.ProviderId = provider.Id; + } + + var providerAbilities = new Dictionary() + { + { provider.Id, new ProviderAbility() { UseEvents = true, Enabled = true } } + }; + sutProvider.GetDependency().GetProviderAbilitiesAsync().Returns(providerAbilities); + sutProvider.GetDependency().UserId.Returns(actingUserId); + sutProvider.GetDependency().IpAddress.Returns(ipAddress); + sutProvider.GetDependency().DeviceType.Returns(deviceType); + sutProvider.GetDependency().ProviderIdForOrg(Arg.Any()).Returns(providerId); + + await sutProvider.Sut.LogProviderOrganizationEventsAsync(providerOrganizations.Select(po => (po, eventType, (DateTime?)date))); + + var expected = providerOrganizations.Select(po => + new EventMessage() + { + DeviceType = deviceType, + IpAddress = ipAddress, + ProviderId = provider.Id, + ProviderOrganizationId = po.Id, + Type = eventType, + ActingUserId = actingUserId, + Date = date + }).ToList(); + + await sutProvider.GetDependency().Received(1).CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual(expected, new[] { "IdempotencyId" }))); + } } diff --git a/test/Infrastructure.EFIntegration.Test/Repositories/OrganizationRepositoryTests.cs b/test/Infrastructure.EFIntegration.Test/Repositories/OrganizationRepositoryTests.cs index 04e314d56..40732709b 100644 --- a/test/Infrastructure.EFIntegration.Test/Repositories/OrganizationRepositoryTests.cs +++ b/test/Infrastructure.EFIntegration.Test/Repositories/OrganizationRepositoryTests.cs @@ -1,4 +1,6 @@ -using Bit.Core.Models.Data.Organizations; +using Bit.Core.Entities; +using Bit.Core.Enums; +using Bit.Core.Models.Data.Organizations; using Bit.Core.Test.AutoFixture.Attributes; using Bit.Infrastructure.EFIntegration.Test.AutoFixture; using Bit.Infrastructure.EFIntegration.Test.Repositories.EqualityComparers; @@ -146,4 +148,42 @@ public class OrganizationRepositoryTests list.Concat(await sqlOrganizationRepo.GetManyAbilitiesAsync()); Assert.True(list.All(x => x.GetType() == typeof(OrganizationAbility))); } + + [CiSkippedTheory, EfOrganizationUserAutoData] + public async void SearchUnassignedAsync_Works(OrganizationUser orgUser, User user, Organization org, + List efOrgUserRepos, List efOrgRepos, List efUserRepos, + SqlRepo.OrganizationUserRepository sqlOrgUserRepo, SqlRepo.OrganizationRepository sqlOrgRepo, SqlRepo.UserRepository sqlUserRepo) + { + orgUser.Type = OrganizationUserType.Owner; + org.PlanType = PlanType.EnterpriseAnnually; + + var efList = new List(); + foreach (var efOrgUserRepo in efOrgUserRepos) + { + var i = efOrgUserRepos.IndexOf(efOrgUserRepo); + var postEfUser = await efUserRepos[i].CreateAsync(user); + var postEfOrg = await efOrgRepos[i].CreateAsync(org); + efOrgUserRepo.ClearChangeTracking(); + + orgUser.UserId = postEfUser.Id; + orgUser.OrganizationId = postEfOrg.Id; + await efOrgUserRepo.CreateAsync(orgUser); + efOrgUserRepo.ClearChangeTracking(); + + efList.AddRange(await efOrgRepos[i].SearchUnassignedToProviderAsync(org.Name, user.Email, 0, 10)); + } + + var postSqlUser = await sqlUserRepo.CreateAsync(user); + var postSqlOrg = await sqlOrgRepo.CreateAsync(org); + + orgUser.UserId = postSqlUser.Id; + orgUser.OrganizationId = postSqlOrg.Id; + await sqlOrgUserRepo.CreateAsync(orgUser); + var sqlResult = await sqlOrgRepo.SearchUnassignedToProviderAsync(org.Name, user.Email, 0, 10); + + Assert.Equal(efOrgRepos.Count, efList.Count); + Assert.True(efList.All(o => o.Name == org.Name)); + Assert.Equal(1, sqlResult.Count); + Assert.True(sqlResult.All(o => o.Name == org.Name)); + } } diff --git a/util/Migrator/DbScripts/2023-01-20_00_OrganizationStatus.sql b/util/Migrator/DbScripts/2023-01-20_00_OrganizationStatus.sql new file mode 100644 index 000000000..be483d4d7 --- /dev/null +++ b/util/Migrator/DbScripts/2023-01-20_00_OrganizationStatus.sql @@ -0,0 +1,291 @@ +--Add column 'Status' to 'Organization' table +IF COL_LENGTH('[dbo].[Organization]', 'Status') IS NULL +BEGIN + ALTER TABLE + [dbo].[Organization] + ADD + [Status] TINYINT NOT NULL CONSTRAINT [DF_Organization_Status] DEFAULT (1) +END +GO + +--Insert value in column 'Status' +CREATE OR ALTER PROCEDURE [dbo].[Organization_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @Identifier NVARCHAR(50), + @Name NVARCHAR(50), + @BusinessName NVARCHAR(50), + @BusinessAddress1 NVARCHAR(50), + @BusinessAddress2 NVARCHAR(50), + @BusinessAddress3 NVARCHAR(50), + @BusinessCountry VARCHAR(2), + @BusinessTaxNumber NVARCHAR(30), + @BillingEmail NVARCHAR(256), + @Plan NVARCHAR(50), + @PlanType TINYINT, + @Seats INT, + @MaxCollections SMALLINT, + @UsePolicies BIT, + @UseSso BIT, + @UseGroups BIT, + @UseDirectory BIT, + @UseEvents BIT, + @UseTotp BIT, + @Use2fa BIT, + @UseApi BIT, + @UseResetPassword BIT, + @SelfHost BIT, + @UsersGetPremium BIT, + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @Enabled BIT, + @LicenseKey VARCHAR(100), + @PublicKey VARCHAR(MAX), + @PrivateKey VARCHAR(MAX), + @TwoFactorProviders NVARCHAR(MAX), + @ExpirationDate DATETIME2(7), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @OwnersNotifiedOfAutoscaling DATETIME2(7), + @MaxAutoscaleSeats INT, + @UseKeyConnector BIT = 0, + @UseScim BIT = 0, + @UseCustomPermissions BIT = 0, + @UseSecretsManager BIT = 0, + @Status TINYINT = 0 +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Organization] + ( + [Id], + [Identifier], + [Name], + [BusinessName], + [BusinessAddress1], + [BusinessAddress2], + [BusinessAddress3], + [BusinessCountry], + [BusinessTaxNumber], + [BillingEmail], + [Plan], + [PlanType], + [Seats], + [MaxCollections], + [UsePolicies], + [UseSso], + [UseGroups], + [UseDirectory], + [UseEvents], + [UseTotp], + [Use2fa], + [UseApi], + [UseResetPassword], + [SelfHost], + [UsersGetPremium], + [Storage], + [MaxStorageGb], + [Gateway], + [GatewayCustomerId], + [GatewaySubscriptionId], + [ReferenceData], + [Enabled], + [LicenseKey], + [PublicKey], + [PrivateKey], + [TwoFactorProviders], + [ExpirationDate], + [CreationDate], + [RevisionDate], + [OwnersNotifiedOfAutoscaling], + [MaxAutoscaleSeats], + [UseKeyConnector], + [UseScim], + [UseCustomPermissions], + [UseSecretsManager], + [Status] + ) + VALUES + ( + @Id, + @Identifier, + @Name, + @BusinessName, + @BusinessAddress1, + @BusinessAddress2, + @BusinessAddress3, + @BusinessCountry, + @BusinessTaxNumber, + @BillingEmail, + @Plan, + @PlanType, + @Seats, + @MaxCollections, + @UsePolicies, + @UseSso, + @UseGroups, + @UseDirectory, + @UseEvents, + @UseTotp, + @Use2fa, + @UseApi, + @UseResetPassword, + @SelfHost, + @UsersGetPremium, + @Storage, + @MaxStorageGb, + @Gateway, + @GatewayCustomerId, + @GatewaySubscriptionId, + @ReferenceData, + @Enabled, + @LicenseKey, + @PublicKey, + @PrivateKey, + @TwoFactorProviders, + @ExpirationDate, + @CreationDate, + @RevisionDate, + @OwnersNotifiedOfAutoscaling, + @MaxAutoscaleSeats, + @UseKeyConnector, + @UseScim, + @UseCustomPermissions, + @UseSecretsManager, + @Status + ) +END +GO + +--Update column 'Status' +CREATE OR ALTER PROCEDURE [dbo].[Organization_Update] + @Id UNIQUEIDENTIFIER, + @Identifier NVARCHAR(50), + @Name NVARCHAR(50), + @BusinessName NVARCHAR(50), + @BusinessAddress1 NVARCHAR(50), + @BusinessAddress2 NVARCHAR(50), + @BusinessAddress3 NVARCHAR(50), + @BusinessCountry VARCHAR(2), + @BusinessTaxNumber NVARCHAR(30), + @BillingEmail NVARCHAR(256), + @Plan NVARCHAR(50), + @PlanType TINYINT, + @Seats INT, + @MaxCollections SMALLINT, + @UsePolicies BIT, + @UseSso BIT, + @UseGroups BIT, + @UseDirectory BIT, + @UseEvents BIT, + @UseTotp BIT, + @Use2fa BIT, + @UseApi BIT, + @UseResetPassword BIT, + @SelfHost BIT, + @UsersGetPremium BIT, + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @Enabled BIT, + @LicenseKey VARCHAR(100), + @PublicKey VARCHAR(MAX), + @PrivateKey VARCHAR(MAX), + @TwoFactorProviders NVARCHAR(MAX), + @ExpirationDate DATETIME2(7), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @OwnersNotifiedOfAutoscaling DATETIME2(7), + @MaxAutoscaleSeats INT, + @UseKeyConnector BIT = 0, + @UseScim BIT = 0, + @UseCustomPermissions BIT = 0, + @UseSecretsManager BIT = 0, + @Status TINYINT = 0 +AS +BEGIN + SET NOCOUNT ON + +UPDATE + [dbo].[Organization] +SET + [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, + [UseGroups] = @UseGroups, + [UseDirectory] = @UseDirectory, + [UseEvents] = @UseEvents, + [UseTotp] = @UseTotp, + [Use2fa] = @Use2fa, + [UseApi] = @UseApi, + [UseResetPassword] = @UseResetPassword, + [SelfHost] = @SelfHost, + [UsersGetPremium] = @UsersGetPremium, + [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, + [OwnersNotifiedOfAutoscaling] = @OwnersNotifiedOfAutoscaling, + [MaxAutoscaleSeats] = @MaxAutoscaleSeats, + [UseKeyConnector] = @UseKeyConnector, + [UseScim] = @UseScim, + [UseCustomPermissions] = @UseCustomPermissions, + [UseSecretsManager] = @UseSecretsManager, + [Status] = @Status +WHERE + [Id] = @Id +END +GO + +--Add column 'Status' +CREATE OR ALTER VIEW [dbo].[ProviderOrganizationOrganizationDetailsView] +AS +SELECT + PO.[Id], + PO.[ProviderId], + PO.[OrganizationId], + O.[Name] OrganizationName, + PO.[Key], + PO.[Settings], + PO.[CreationDate], + PO.[RevisionDate], + (SELECT COUNT(1) FROM [dbo].[OrganizationUser] OU WHERE OU.OrganizationId = PO.OrganizationId AND OU.Status = 2) UserCount, + O.[Seats], + O.[Plan], + O.[Status] +FROM + [dbo].[ProviderOrganization] PO + LEFT JOIN + [dbo].[Organization] O ON O.[Id] = PO.[OrganizationId] +GO + + diff --git a/util/Migrator/DbScripts/2023-02-06_00_ProviderReadByOrganizationId.sql b/util/Migrator/DbScripts/2023-02-06_00_ProviderReadByOrganizationId.sql new file mode 100644 index 000000000..9f8e5c42c --- /dev/null +++ b/util/Migrator/DbScripts/2023-02-06_00_ProviderReadByOrganizationId.sql @@ -0,0 +1,16 @@ +CREATE OR ALTER PROCEDURE [dbo].[Provider_ReadByOrganizationId] + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + +SELECT + P.* +FROM + [dbo].[ProviderView] P +INNER JOIN + [dbo].[ProviderOrganization] PO ON PO.[ProviderId] = P.[Id] +WHERE + PO.[OrganizationId] = @OrganizationId +END +GO \ No newline at end of file diff --git a/util/Migrator/DbScripts/2023-03-08_OrganizationProviderType.sql b/util/Migrator/DbScripts/2023-03-08_OrganizationProviderType.sql new file mode 100644 index 000000000..685b2d3b2 --- /dev/null +++ b/util/Migrator/DbScripts/2023-03-08_OrganizationProviderType.sql @@ -0,0 +1,82 @@ +CREATE OR ALTER VIEW [dbo].[OrganizationUserOrganizationDetailsView] +AS +SELECT + OU.[UserId], + OU.[OrganizationId], + O.[Name], + O.[Enabled], + O.[PlanType], + O.[UsePolicies], + O.[UseSso], + O.[UseKeyConnector], + O.[UseScim], + O.[UseGroups], + O.[UseDirectory], + O.[UseEvents], + O.[UseTotp], + O.[Use2fa], + O.[UseApi], + O.[UseResetPassword], + O.[SelfHost], + O.[UsersGetPremium], + O.[UseCustomPermissions], + O.[UseSecretsManager], + O.[Seats], + O.[MaxCollections], + O.[MaxStorageGb], + O.[Identifier], + OU.[Key], + OU.[ResetPasswordKey], + O.[PublicKey], + O.[PrivateKey], + OU.[Status], + OU.[Type], + SU.[ExternalId] SsoExternalId, + OU.[Permissions], + PO.[ProviderId], + P.[Name] ProviderName, + P.[Type] ProviderType, + SS.[Data] SsoConfig, + OS.[FriendlyName] FamilySponsorshipFriendlyName, + OS.[LastSyncDate] FamilySponsorshipLastSyncDate, + OS.[ToDelete] FamilySponsorshipToDelete, + OS.[ValidUntil] FamilySponsorshipValidUntil, + OU.[AccessSecretsManager] +FROM + [dbo].[OrganizationUser] OU +LEFT JOIN + [dbo].[Organization] O ON O.[Id] = OU.[OrganizationId] +LEFT JOIN + [dbo].[SsoUser] SU ON SU.[UserId] = OU.[UserId] AND SU.[OrganizationId] = OU.[OrganizationId] +LEFT JOIN + [dbo].[ProviderOrganization] PO ON PO.[OrganizationId] = O.[Id] +LEFT JOIN + [dbo].[Provider] P ON P.[Id] = PO.[ProviderId] +LEFT JOIN + [dbo].[SsoConfig] SS ON SS.[OrganizationId] = OU.[OrganizationId] +LEFT JOIN + [dbo].[OrganizationSponsorship] OS ON OS.[SponsoringOrganizationUserID] = OU.[Id] +GO + +CREATE OR ALTER PROCEDURE [dbo].[ProviderOrganizationProviderDetails_ReadByUserId] + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + +SELECT + PO.Id, + PO.OrganizationId, + PO.ProviderId, + P.Name as ProviderName, + P.[Type] as ProviderType +FROM + [dbo].[ProviderOrganizationView] PO +INNER JOIN + [dbo].[OrganizationUser] OU ON PO.OrganizationId = OU.OrganizationId +INNER JOIN + [dbo].[Provider] P ON PO.ProviderId = P.Id +WHERE + OU.UserId = @UserId +END +GO \ No newline at end of file diff --git a/util/Migrator/DbScripts/2023-03-22_00_ProviderAddExistingOrganizations.sql b/util/Migrator/DbScripts/2023-03-22_00_ProviderAddExistingOrganizations.sql new file mode 100644 index 000000000..72b052594 --- /dev/null +++ b/util/Migrator/DbScripts/2023-03-22_00_ProviderAddExistingOrganizations.sql @@ -0,0 +1,54 @@ +-- Drop existing SPROC +IF OBJECT_ID('[dbo].[Organization_UnassignedToProviderSearch]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Organization_UnassignedToProviderSearch] +END +GO + +CREATE PROCEDURE [dbo].[Organization_UnassignedToProviderSearch] + @Name NVARCHAR(50), + @OwnerEmail NVARCHAR(256), + @Skip INT = 0, + @Take INT = 25 +WITH RECOMPILE +AS +BEGIN + SET NOCOUNT ON + DECLARE @NameLikeSearch NVARCHAR(55) = '%' + @Name + '%' + DECLARE @OwnerLikeSearch NVARCHAR(55) = @OwnerEmail + '%' + + IF @OwnerEmail IS NOT NULL + BEGIN + SELECT + O.* + FROM + [dbo].[OrganizationView] O + INNER JOIN + [dbo].[OrganizationUser] OU ON O.[Id] = OU.[OrganizationId] + INNER JOIN + [dbo].[User] U ON U.[Id] = OU.[UserId] + WHERE + O.[PlanType] >= 8 AND O.[PlanType] <= 11 -- Get 'Team' and 'Enterprise' Organizations + AND NOT EXISTS (SELECT * FROM [dbo].[ProviderOrganizationView] PO WHERE PO.[OrganizationId] = O.[Id]) + AND (@Name IS NULL OR O.[Name] LIKE @NameLikeSearch) + AND (U.[Email] LIKE @OwnerLikeSearch) + ORDER BY O.[CreationDate] DESC + OFFSET @Skip ROWS + FETCH NEXT @Take ROWS ONLY + END + ELSE + BEGIN + SELECT + O.* + FROM + [dbo].[OrganizationView] O + WHERE + O.[PlanType] >= 8 AND O.[PlanType] <= 11 -- Get 'Team' and 'Enterprise' Organizations + AND NOT EXISTS (SELECT * FROM [dbo].[ProviderOrganizationView] PO WHERE PO.[OrganizationId] = O.[Id]) + AND (@Name IS NULL OR O.[Name] LIKE @NameLikeSearch) + ORDER BY O.[CreationDate] DESC + OFFSET @Skip ROWS + FETCH NEXT @Take ROWS ONLY + END +END +GO \ No newline at end of file diff --git a/util/Migrator/DbScripts/2023-04-13_00_ProviderReadCountByOrganizationIds.sql b/util/Migrator/DbScripts/2023-04-13_00_ProviderReadCountByOrganizationIds.sql new file mode 100644 index 000000000..7650523e3 --- /dev/null +++ b/util/Migrator/DbScripts/2023-04-13_00_ProviderReadCountByOrganizationIds.sql @@ -0,0 +1,19 @@ +CREATE OR ALTER PROCEDURE [dbo].[ProviderOrganization_ReadCountByOrganizationIds] + @Ids AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + IF (SELECT COUNT(1) FROM @Ids) < 1 + BEGIN + RETURN(-1) + END + + SELECT + COUNT(1) + FROM + [dbo].[ProviderOrganizationView] + WHERE + [OrganizationId] IN (SELECT [Id] FROM @Ids) +END +GO \ No newline at end of file diff --git a/util/MySqlMigrations/Migrations/20230120160248_OrganizationStatus.Designer.cs b/util/MySqlMigrations/Migrations/20230120160248_OrganizationStatus.Designer.cs new file mode 100644 index 000000000..6f7042d07 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20230120160248_OrganizationStatus.Designer.cs @@ -0,0 +1,2107 @@ +// +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("20230120160248_OrganizationStatus")] + partial class OrganizationStatus + { + 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("Status") + .HasColumnType("tinyint unsigned"); + + 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("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.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("UnknownDeviceVerificationEnabled") + .HasColumnType("tinyint(1)"); + + 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/20230120160248_OrganizationStatus.cs b/util/MySqlMigrations/Migrations/20230120160248_OrganizationStatus.cs new file mode 100644 index 000000000..df92f139f --- /dev/null +++ b/util/MySqlMigrations/Migrations/20230120160248_OrganizationStatus.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.MySqlMigrations.Migrations; + +public partial class OrganizationStatus : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Status", + table: "Organization", + type: "tinyint unsigned", + nullable: false, + defaultValue: (byte)1); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Status", + table: "Organization"); + } +} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index 6ff68a0b3..0ede57ecd 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -609,6 +609,9 @@ namespace Bit.MySqlMigrations.Migrations b.Property("SelfHost") .HasColumnType("tinyint(1)"); + b.Property("Status") + .HasColumnType("tinyint unsigned"); + b.Property("Storage") .HasColumnType("bigint"); diff --git a/util/PostgresMigrations/Migrations/20230120160253_OrganizationStatus.Designer.cs b/util/PostgresMigrations/Migrations/20230120160253_OrganizationStatus.Designer.cs new file mode 100644 index 000000000..61dfa2fdf --- /dev/null +++ b/util/PostgresMigrations/Migrations/20230120160253_OrganizationStatus.Designer.cs @@ -0,0 +1,2118 @@ +// +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("20230120160253_OrganizationStatus")] + partial class OrganizationStatus + { + 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("Status") + .HasColumnType("smallint"); + + 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("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.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("UnknownDeviceVerificationEnabled") + .HasColumnType("boolean"); + + 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/20230120160253_OrganizationStatus.cs b/util/PostgresMigrations/Migrations/20230120160253_OrganizationStatus.cs new file mode 100644 index 000000000..3129c1f83 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20230120160253_OrganizationStatus.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.PostgresMigrations.Migrations; + +public partial class OrganizationStatus : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Status", + table: "Organization", + type: "smallint", + nullable: false, + defaultValue: (byte)1); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Status", + table: "Organization"); + } +} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index a38820b08..fa9895de2 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -540,6 +540,9 @@ namespace Bit.PostgresMigrations.Migrations b.Property("SelfHost") .HasColumnType("boolean"); + b.Property("Status") + .HasColumnType("smallint"); + b.Property("Storage") .HasColumnType("bigint"); diff --git a/util/SqliteMigrations/Migrations/20230120160257_OrganizationStatus.Designer.cs b/util/SqliteMigrations/Migrations/20230120160257_OrganizationStatus.Designer.cs new file mode 100644 index 000000000..6100f6183 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20230120160257_OrganizationStatus.Designer.cs @@ -0,0 +1,2105 @@ +// +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("20230120160257_OrganizationStatus")] + partial class OrganizationStatus + { + 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("Status") + .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("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.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("UnknownDeviceVerificationEnabled") + .HasColumnType("INTEGER"); + + 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/20230120160257_OrganizationStatus.cs b/util/SqliteMigrations/Migrations/20230120160257_OrganizationStatus.cs new file mode 100644 index 000000000..334bdd4c8 --- /dev/null +++ b/util/SqliteMigrations/Migrations/20230120160257_OrganizationStatus.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Bit.SqliteMigrations.Migrations; + +public partial class OrganizationStatus : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Status", + table: "Organization", + type: "INTEGER", + nullable: false, + defaultValue: (byte)1); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Status", + table: "Organization"); + } +} diff --git a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs index 792f5c934..0952d3c8a 100644 --- a/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -533,6 +533,9 @@ namespace Bit.SqliteMigrations.Migrations b.Property("SelfHost") .HasColumnType("INTEGER"); + b.Property("Status") + .HasColumnType("INTEGER"); + b.Property("Storage") .HasColumnType("INTEGER");
NameNameStatus + @if (Model.Provider.Type == ProviderType.Reseller) + { + + } +
- @org.OrganizationName + @org.OrganizationName + + @org.Status + +
+ @if (org.Status == OrganizationStatusType.Pending) + { + + + + } + else + { + + } +