diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index a1119ca479..304c6d3b58 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -7,7 +7,7 @@ "commands": ["swagger"] }, "dotnet-ef": { - "version": "8.0.1", + "version": "8.0.2", "commands": ["dotnet-ef"] } } diff --git a/.github/codecov.yml b/.github/codecov.yml index 3a606f3b5a..429f76d677 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,2 +1,3 @@ ignore: - "test" # Tests + - "util" # Utils (migrators) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c63ebd669f..eff4695cfd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -119,6 +119,8 @@ jobs: build-docker: name: Build Docker images runs-on: ubuntu-22.04 + permissions: + security-events: write needs: build-artifacts strategy: fail-fast: false @@ -173,7 +175,7 @@ jobs: - name: Check out repo uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - name: Check Branch to Publish + - name: Check branch to publish env: PUBLISH_BRANCHES: "main,rc,hotfix-rc" id: publish-branch-check @@ -192,7 +194,7 @@ jobs: with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} - - name: Login to PROD ACR + - name: Log in to ACR - production subscription run: az acr login -n bitwardenprod - name: Log in to Azure - CI subscription @@ -200,7 +202,7 @@ jobs: with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - name: Retrieve github PAT secrets + - name: Retrieve GitHub PAT secrets id: retrieve-secret-pat uses: bitwarden/gh-actions/get-keyvault-secrets@main with: @@ -232,19 +234,20 @@ jobs: echo "PROJECT_NAME: $PROJECT_NAME" echo "project_name=$PROJECT_NAME" >> $GITHUB_OUTPUT - - name: Generate image name(s) - id: image-names + - name: Generate image tags(s) + id: image-tags env: IMAGE_TAG: ${{ steps.tag.outputs.image_tag }} PROJECT_NAME: ${{ steps.setup.outputs.project_name }} SHA: ${{ github.sha }} run: | - NAMES="${_AZ_REGISTRY}/${PROJECT_NAME}:${IMAGE_TAG}" + TAGS="${_AZ_REGISTRY}/${PROJECT_NAME}:${IMAGE_TAG}" + echo "primary_tag=$TAGS" >> $GITHUB_OUTPUT if [[ "${IMAGE_TAG}" == "dev" ]]; then SHORT_SHA=$(git rev-parse --short ${SHA}) - NAMES=$NAMES",${_AZ_REGISTRY}/${PROJECT_NAME}:dev-${SHORT_SHA}" + TAGS=$TAGS",${_AZ_REGISTRY}/${PROJECT_NAME}:dev-${SHORT_SHA}" fi - echo "names=$NAMES" >> $GITHUB_OUTPUT + echo "tags=$TAGS" >> $GITHUB_OUTPUT - name: Get build artifact if: ${{ matrix.dotnet }} @@ -266,10 +269,23 @@ jobs: file: ${{ matrix.base_path }}/${{ matrix.project_name }}/Dockerfile platforms: linux/amd64 push: true - tags: ${{ steps.image-names.outputs.names }} + tags: ${{ steps.image-tags.outputs.tags }} secrets: | "GH_PAT=${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}" + - name: Scan Docker image + id: container-scan + uses: anchore/scan-action@3343887d815d7b07465f6fdcd395bd66508d486a # v3.6.4 + with: + image: ${{ steps.image-tags.outputs.primary_tag }} + fail-build: false + output-format: sarif + + - name: Upload Grype results to GitHub + uses: github/codeql-action/upload-sarif@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2 + with: + sarif_file: ${{ steps.container-scan.outputs.sarif }} + upload: name: Upload runs-on: ubuntu-22.04 @@ -286,7 +302,7 @@ jobs: with: creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }} - - name: Login to PROD ACR + - name: Log in to ACR - production subscription run: az acr login -n $_AZ_REGISTRY --only-show-errors - name: Make Docker stubs @@ -453,7 +469,7 @@ jobs: with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - name: Retrieve github PAT secrets + - name: Retrieve GitHub PAT secrets id: retrieve-secret-pat uses: bitwarden/gh-actions/get-keyvault-secrets@main with: @@ -486,7 +502,7 @@ jobs: with: creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - - name: Retrieve github PAT secrets + - name: Retrieve GitHub PAT secrets id: retrieve-secret-pat uses: bitwarden/gh-actions/get-keyvault-secrets@main with: diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml new file mode 100644 index 0000000000..ff36b004de --- /dev/null +++ b/.github/workflows/scan.yml @@ -0,0 +1,60 @@ +name: Scan + +on: + workflow_dispatch: + push: + branches: + - "main" + - "rc" + - "hotfix-rc" + pull_request: + +permissions: read-all + +jobs: + sast: + name: SAST scan + runs-on: ubuntu-22.04 + permissions: + security-events: write + + steps: + - name: Check out repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Scan with Checkmarx + uses: checkmarx/ast-github-action@749fec53e0db0f6404a97e2e0807c3e80e3583a7 #2.0.23 + env: + INCREMENTAL: "${{ github.event_name == 'pull_request' && '--sast-incremental' || '' }}" + with: + project_name: ${{ github.repository }} + cx_tenant: ${{ secrets.CHECKMARX_TENANT }} + base_uri: https://ast.checkmarx.net/ + cx_client_id: ${{ secrets.CHECKMARX_CLIENT_ID }} + cx_client_secret: ${{ secrets.CHECKMARX_SECRET }} + additional_params: --report-format sarif --output-path . ${{ env.INCREMENTAL }} + + - name: Upload Checkmarx results to GitHub + uses: github/codeql-action/upload-sarif@b7bf0a3ed3ecfa44160715d7c442788f65f0f923 # v3.23.2 + with: + sarif_file: cx_result.sarif + + quality: + name: Quality scan + runs-on: ubuntu-22.04 + + steps: + - name: Check out repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Scan with SonarCloud + uses: sonarsource/sonarcloud-github-action@49e6cd3b187936a73b8280d59ffd9da69df63ec9 # v2.1.1 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + args: > + -Dsonar.organization=${{ github.repository_owner }} + -Dsonar.projectKey=${{ github.repository_owner }}_${{ github.event.repository.name }} + -Dsonar.test.exclusions=test/** + -Dsonar.tests=test/ diff --git a/bitwarden-server.sln b/bitwarden-server.sln index 6a45e21eb4..154ffe3614 100644 --- a/bitwarden-server.sln +++ b/bitwarden-server.sln @@ -116,6 +116,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqliteMigrations", "util\Sq EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MsSqlMigratorUtility", "util\MsSqlMigratorUtility\MsSqlMigratorUtility.csproj", "{D9A2CCBB-FB0A-4BBA-A9ED-BA9FF277C880}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Admin.Test", "test\Admin.Test\Admin.Test.csproj", "{52D22B52-26D3-463A-8EB5-7FDC849D3761}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Events.Test", "test\Events.Test\Events.Test.csproj", "{916AFD8C-30AF-49B6-A5C9-28CA1B5D9298}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventsProcessor.Test", "test\EventsProcessor.Test\EventsProcessor.Test.csproj", "{81673EFB-7134-4B4B-A32F-1EA05F0EF3CE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Notifications.Test", "test\Notifications.Test\Notifications.Test.csproj", "{90D85D8F-5577-4570-A96E-5A2E185F0F6F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -284,6 +292,22 @@ Global {D9A2CCBB-FB0A-4BBA-A9ED-BA9FF277C880}.Debug|Any CPU.Build.0 = Debug|Any CPU {D9A2CCBB-FB0A-4BBA-A9ED-BA9FF277C880}.Release|Any CPU.ActiveCfg = Release|Any CPU {D9A2CCBB-FB0A-4BBA-A9ED-BA9FF277C880}.Release|Any CPU.Build.0 = Release|Any CPU + {52D22B52-26D3-463A-8EB5-7FDC849D3761}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52D22B52-26D3-463A-8EB5-7FDC849D3761}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52D22B52-26D3-463A-8EB5-7FDC849D3761}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52D22B52-26D3-463A-8EB5-7FDC849D3761}.Release|Any CPU.Build.0 = Release|Any CPU + {916AFD8C-30AF-49B6-A5C9-28CA1B5D9298}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {916AFD8C-30AF-49B6-A5C9-28CA1B5D9298}.Debug|Any CPU.Build.0 = Debug|Any CPU + {916AFD8C-30AF-49B6-A5C9-28CA1B5D9298}.Release|Any CPU.ActiveCfg = Release|Any CPU + {916AFD8C-30AF-49B6-A5C9-28CA1B5D9298}.Release|Any CPU.Build.0 = Release|Any CPU + {81673EFB-7134-4B4B-A32F-1EA05F0EF3CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81673EFB-7134-4B4B-A32F-1EA05F0EF3CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81673EFB-7134-4B4B-A32F-1EA05F0EF3CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81673EFB-7134-4B4B-A32F-1EA05F0EF3CE}.Release|Any CPU.Build.0 = Release|Any CPU + {90D85D8F-5577-4570-A96E-5A2E185F0F6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90D85D8F-5577-4570-A96E-5A2E185F0F6F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90D85D8F-5577-4570-A96E-5A2E185F0F6F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90D85D8F-5577-4570-A96E-5A2E185F0F6F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -329,6 +353,10 @@ Global {7E9A7DD5-EB78-4AC5-BFD5-64573FD2533B} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F} {07143DFA-F242-47A4-A15E-39C9314D4140} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E} {D9A2CCBB-FB0A-4BBA-A9ED-BA9FF277C880} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E} + {52D22B52-26D3-463A-8EB5-7FDC849D3761} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F} + {916AFD8C-30AF-49B6-A5C9-28CA1B5D9298} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F} + {81673EFB-7134-4B4B-A32F-1EA05F0EF3CE} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F} + {90D85D8F-5577-4570-A96E-5A2E185F0F6F} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E01CBF68-2E20-425F-9EDB-E0A6510CA92F} diff --git a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ServiceAccountRepository.cs b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ServiceAccountRepository.cs index e9a7c183b5..e1053b2477 100644 --- a/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ServiceAccountRepository.cs +++ b/bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ServiceAccountRepository.cs @@ -84,22 +84,29 @@ public class ServiceAccountRepository : Repository ids) { + var targetIds = ids.ToList(); using var scope = ServiceScopeFactory.CreateScope(); var dbContext = GetDatabaseContext(scope); + await using var transaction = await dbContext.Database.BeginTransactionAsync(); + // Policies can't have a cascade delete, so we need to delete them manually. - var policies = dbContext.AccessPolicies.Where(ap => - ((ServiceAccountProjectAccessPolicy)ap).ServiceAccountId.HasValue && ids.Contains(((ServiceAccountProjectAccessPolicy)ap).ServiceAccountId!.Value) || - ((GroupServiceAccountAccessPolicy)ap).GrantedServiceAccountId.HasValue && ids.Contains(((GroupServiceAccountAccessPolicy)ap).GrantedServiceAccountId!.Value) || - ((UserServiceAccountAccessPolicy)ap).GrantedServiceAccountId.HasValue && ids.Contains(((UserServiceAccountAccessPolicy)ap).GrantedServiceAccountId!.Value)); - dbContext.RemoveRange(policies); + await dbContext.AccessPolicies.Where(ap => + targetIds.Contains(((ServiceAccountProjectAccessPolicy)ap).ServiceAccountId!.Value) || + targetIds.Contains(((ServiceAccountSecretAccessPolicy)ap).ServiceAccountId!.Value) || + targetIds.Contains(((GroupServiceAccountAccessPolicy)ap).GrantedServiceAccountId!.Value) || + targetIds.Contains(((UserServiceAccountAccessPolicy)ap).GrantedServiceAccountId!.Value)) + .ExecuteDeleteAsync(); - var apiKeys = dbContext.ApiKeys.Where(a => a.ServiceAccountId.HasValue && ids.Contains(a.ServiceAccountId!.Value)); - dbContext.RemoveRange(apiKeys); + await dbContext.ApiKeys + .Where(a => targetIds.Contains(a.ServiceAccountId!.Value)) + .ExecuteDeleteAsync(); - var serviceAccounts = dbContext.ServiceAccount.Where(c => ids.Contains(c.Id)); - dbContext.RemoveRange(serviceAccounts); - await dbContext.SaveChangesAsync(); + await dbContext.ServiceAccount + .Where(c => targetIds.Contains(c.Id)) + .ExecuteDeleteAsync(); + + await transaction.CommitAsync(); } public async Task<(bool Read, bool Write)> AccessToServiceAccountAsync(Guid id, Guid userId, diff --git a/perf/MicroBenchmarks/Identity/IdentityServer/StaticClientStoreTests.cs b/perf/MicroBenchmarks/Identity/IdentityServer/StaticClientStoreTests.cs new file mode 100644 index 0000000000..1fcd6a9f99 --- /dev/null +++ b/perf/MicroBenchmarks/Identity/IdentityServer/StaticClientStoreTests.cs @@ -0,0 +1,27 @@ +using BenchmarkDotNet.Attributes; +using Bit.Core.Settings; +using Bit.Identity.IdentityServer; +using Duende.IdentityServer.Models; + +namespace Bit.MicroBenchmarks.Identity.IdentityServer; + +public class StaticClientStoreTests +{ + private readonly StaticClientStore _store; + + public StaticClientStoreTests() + { + _store = new StaticClientStore(new GlobalSettings()); + } + + [Params("mobile", "connector", "invalid", "a_much_longer_invalid_value_that_i_am_making_up", "WEB", "")] + public string? ClientId { get; set; } + + [Benchmark] + public Client? TryGetValue() + { + return _store.ApiClients.TryGetValue(ClientId, out var client) + ? client + : null; + } +} diff --git a/src/Admin/Controllers/OrganizationsController.cs b/src/Admin/AdminConsole/Controllers/OrganizationsController.cs similarity index 98% rename from src/Admin/Controllers/OrganizationsController.cs rename to src/Admin/AdminConsole/Controllers/OrganizationsController.cs index 3fba88006d..ac4682edb6 100644 --- a/src/Admin/Controllers/OrganizationsController.cs +++ b/src/Admin/AdminConsole/Controllers/OrganizationsController.cs @@ -1,5 +1,5 @@ -using Bit.Admin.Enums; -using Bit.Admin.Models; +using Bit.Admin.AdminConsole.Models; +using Bit.Admin.Enums; using Bit.Admin.Services; using Bit.Admin.Utilities; using Bit.Core.AdminConsole.Entities; @@ -23,7 +23,7 @@ using Bit.Core.Vault.Repositories; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Admin.Controllers; +namespace Bit.Admin.AdminConsole.Controllers; [Authorize] public class OrganizationsController : Controller @@ -38,7 +38,6 @@ public class OrganizationsController : Controller private readonly IGroupRepository _groupRepository; private readonly IPolicyRepository _policyRepository; private readonly IPaymentService _paymentService; - private readonly ILicensingService _licensingService; private readonly IApplicationCacheService _applicationCacheService; private readonly GlobalSettings _globalSettings; private readonly IReferenceEventService _referenceEventService; @@ -65,7 +64,6 @@ public class OrganizationsController : Controller IGroupRepository groupRepository, IPolicyRepository policyRepository, IPaymentService paymentService, - ILicensingService licensingService, IApplicationCacheService applicationCacheService, GlobalSettings globalSettings, IReferenceEventService referenceEventService, @@ -91,7 +89,6 @@ public class OrganizationsController : Controller _groupRepository = groupRepository; _policyRepository = policyRepository; _paymentService = paymentService; - _licensingService = licensingService; _applicationCacheService = applicationCacheService; _globalSettings = globalSettings; _referenceEventService = referenceEventService; diff --git a/src/Admin/Controllers/ProviderOrganizationsController.cs b/src/Admin/AdminConsole/Controllers/ProviderOrganizationsController.cs similarity index 98% rename from src/Admin/Controllers/ProviderOrganizationsController.cs rename to src/Admin/AdminConsole/Controllers/ProviderOrganizationsController.cs index e21e1297f6..b2deede4a4 100644 --- a/src/Admin/Controllers/ProviderOrganizationsController.cs +++ b/src/Admin/AdminConsole/Controllers/ProviderOrganizationsController.cs @@ -8,7 +8,7 @@ using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Admin.Controllers; +namespace Bit.Admin.AdminConsole.Controllers; [Authorize] [SelfHosted(NotSelfHostedOnly = true)] diff --git a/src/Admin/Controllers/ProvidersController.cs b/src/Admin/AdminConsole/Controllers/ProvidersController.cs similarity index 92% rename from src/Admin/Controllers/ProvidersController.cs rename to src/Admin/AdminConsole/Controllers/ProvidersController.cs index 8fa02e9b32..c228149ea2 100644 --- a/src/Admin/Controllers/ProvidersController.cs +++ b/src/Admin/AdminConsole/Controllers/ProvidersController.cs @@ -1,6 +1,7 @@ -using Bit.Admin.Enums; -using Bit.Admin.Models; +using Bit.Admin.AdminConsole.Models; +using Bit.Admin.Enums; using Bit.Admin.Utilities; +using Bit.Core; using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Providers.Interfaces; @@ -14,7 +15,7 @@ using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Admin.Controllers; +namespace Bit.Admin.AdminConsole.Controllers; [Authorize] [SelfHosted(NotSelfHostedOnly = true)] @@ -31,6 +32,7 @@ public class ProvidersController : Controller private readonly IReferenceEventService _referenceEventService; private readonly IUserService _userService; private readonly ICreateProviderCommand _createProviderCommand; + private readonly IFeatureService _featureService; public ProvidersController( IOrganizationRepository organizationRepository, @@ -43,7 +45,8 @@ public class ProvidersController : Controller IApplicationCacheService applicationCacheService, IReferenceEventService referenceEventService, IUserService userService, - ICreateProviderCommand createProviderCommand) + ICreateProviderCommand createProviderCommand, + IFeatureService featureService) { _organizationRepository = organizationRepository; _organizationService = organizationService; @@ -56,6 +59,7 @@ public class ProvidersController : Controller _referenceEventService = referenceEventService; _userService = userService; _createProviderCommand = createProviderCommand; + _featureService = featureService; } [RequirePermission(Permission.Provider_List_View)] @@ -236,7 +240,9 @@ public class ProvidersController : Controller return RedirectToAction("Index"); } - var organization = model.CreateOrganization(provider); + var flexibleCollectionsSignupEnabled = _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsSignup); + var flexibleCollectionsV1Enabled = _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollectionsV1); + var organization = model.CreateOrganization(provider, flexibleCollectionsSignupEnabled, flexibleCollectionsV1Enabled); await _organizationService.CreatePendingOrganization(organization, model.Owners, User, _userService, model.SalesAssistedTrialStarted); await _providerService.AddOrganization(providerId, organization.Id, null); diff --git a/src/Admin/Models/CreateProviderModel.cs b/src/Admin/AdminConsole/Models/CreateProviderModel.cs similarity index 98% rename from src/Admin/Models/CreateProviderModel.cs rename to src/Admin/AdminConsole/Models/CreateProviderModel.cs index 8eddd014cd..7efd34feb1 100644 --- a/src/Admin/Models/CreateProviderModel.cs +++ b/src/Admin/AdminConsole/Models/CreateProviderModel.cs @@ -3,7 +3,7 @@ using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Enums.Provider; using Bit.SharedWeb.Utilities; -namespace Bit.Admin.Models; +namespace Bit.Admin.AdminConsole.Models; public class CreateProviderModel : IValidatableObject { diff --git a/src/Admin/Models/OrganizationEditModel.cs b/src/Admin/AdminConsole/Models/OrganizationEditModel.cs similarity index 89% rename from src/Admin/Models/OrganizationEditModel.cs rename to src/Admin/AdminConsole/Models/OrganizationEditModel.cs index 559b5b6868..575bd34e46 100644 --- a/src/Admin/Models/OrganizationEditModel.cs +++ b/src/Admin/AdminConsole/Models/OrganizationEditModel.cs @@ -11,7 +11,7 @@ using Bit.Core.Utilities; using Bit.Core.Vault.Entities; using Bit.SharedWeb.Utilities; -namespace Bit.Admin.Models; +namespace Bit.Admin.AdminConsole.Models; public class OrganizationEditModel : OrganizationViewModel { @@ -164,11 +164,22 @@ public class OrganizationEditModel : OrganizationViewModel { "baseServiceAccount", p.SecretsManager.BaseServiceAccount } }); - public Organization CreateOrganization(Provider provider) + public Organization CreateOrganization(Provider provider, bool flexibleCollectionsSignupEnabled, bool flexibleCollectionsV1Enabled) { BillingEmail = provider.BillingEmail; - return ToOrganization(new Organization()); + var newOrg = new Organization + { + // This feature flag indicates that new organizations should be automatically onboarded to + // Flexible Collections enhancements + FlexibleCollections = flexibleCollectionsSignupEnabled, + // These collection management settings smooth the migration for existing organizations by disabling some FC behavior. + // If the organization is onboarded to Flexible Collections on signup, we turn them OFF to enable all new behaviour. + // If the organization is NOT onboarded now, they will have to be migrated later, so they default to ON to limit FC changes on migration. + LimitCollectionCreationDeletion = !flexibleCollectionsSignupEnabled, + AllowAdminAccessToAllCollectionItems = !flexibleCollectionsV1Enabled + }; + return ToOrganization(newOrg); } public Organization ToOrganization(Organization existingOrganization) diff --git a/src/Admin/Models/OrganizationSelectableViewModel.cs b/src/Admin/AdminConsole/Models/OrganizationSelectableViewModel.cs similarity index 78% rename from src/Admin/Models/OrganizationSelectableViewModel.cs rename to src/Admin/AdminConsole/Models/OrganizationSelectableViewModel.cs index e43bb19723..2c148e04dd 100644 --- a/src/Admin/Models/OrganizationSelectableViewModel.cs +++ b/src/Admin/AdminConsole/Models/OrganizationSelectableViewModel.cs @@ -1,6 +1,6 @@ using Bit.Core.AdminConsole.Entities; -namespace Bit.Admin.Models; +namespace Bit.Admin.AdminConsole.Models; public class OrganizationSelectableViewModel : Organization { diff --git a/src/Admin/Models/OrganizationUnassignedToProviderSearchViewModel.cs b/src/Admin/AdminConsole/Models/OrganizationUnassignedToProviderSearchViewModel.cs similarity index 84% rename from src/Admin/Models/OrganizationUnassignedToProviderSearchViewModel.cs rename to src/Admin/AdminConsole/Models/OrganizationUnassignedToProviderSearchViewModel.cs index 73aee284c8..cbf15a4776 100644 --- a/src/Admin/Models/OrganizationUnassignedToProviderSearchViewModel.cs +++ b/src/Admin/AdminConsole/Models/OrganizationUnassignedToProviderSearchViewModel.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; +using Bit.Admin.Models; -namespace Bit.Admin.Models; +namespace Bit.Admin.AdminConsole.Models; public class OrganizationUnassignedToProviderSearchViewModel : PagedModel { diff --git a/src/Admin/Models/OrganizationViewModel.cs b/src/Admin/AdminConsole/Models/OrganizationViewModel.cs similarity index 78% rename from src/Admin/Models/OrganizationViewModel.cs rename to src/Admin/AdminConsole/Models/OrganizationViewModel.cs index 0bad3650fd..7706389d10 100644 --- a/src/Admin/Models/OrganizationViewModel.cs +++ b/src/Admin/AdminConsole/Models/OrganizationViewModel.cs @@ -5,15 +5,19 @@ using Bit.Core.Enums; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Vault.Entities; -namespace Bit.Admin.Models; +namespace Bit.Admin.AdminConsole.Models; public class OrganizationViewModel { - public OrganizationViewModel() { } + public OrganizationViewModel() + { + } public OrganizationViewModel(Organization org, Provider provider, IEnumerable connections, - IEnumerable orgUsers, IEnumerable ciphers, IEnumerable collections, - IEnumerable groups, IEnumerable policies, int secretsCount, int projectCount, int serviceAccountsCount, + IEnumerable orgUsers, IEnumerable ciphers, + IEnumerable collections, + IEnumerable groups, IEnumerable policies, int secretsCount, int projectCount, + int serviceAccountsCount, int occupiedSmSeatsCount) { @@ -34,12 +38,12 @@ public class OrganizationViewModel : OrganizationUserStatusType.Confirmed; Owners = string.Join(", ", orgUsers - .Where(u => u.Type == OrganizationUserType.Owner && u.Status == organizationUserStatus) - .Select(u => u.Email)); + .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 == organizationUserStatus) - .Select(u => u.Email)); + .Where(u => u.Type == OrganizationUserType.Admin && u.Status == organizationUserStatus) + .Select(u => u.Email)); SecretsCount = secretsCount; ProjectsCount = projectCount; ServiceAccountsCount = serviceAccountsCount; @@ -65,4 +69,14 @@ public class OrganizationViewModel public int ServiceAccountsCount { get; set; } public int OccupiedSmSeatsCount { get; set; } public bool UseSecretsManager => Organization.UseSecretsManager; + + public string GetCollectionManagementSetting(bool collectionManagementSetting) + { + if (!Organization.FlexibleCollections) + { + return "N/A"; + } + + return collectionManagementSetting ? "On" : "Off"; + } } diff --git a/src/Admin/Models/OrganizationsModel.cs b/src/Admin/AdminConsole/Models/OrganizationsModel.cs similarity index 71% rename from src/Admin/Models/OrganizationsModel.cs rename to src/Admin/AdminConsole/Models/OrganizationsModel.cs index ea2a370d51..147c5275f8 100644 --- a/src/Admin/Models/OrganizationsModel.cs +++ b/src/Admin/AdminConsole/Models/OrganizationsModel.cs @@ -1,6 +1,7 @@ -using Bit.Core.AdminConsole.Entities; +using Bit.Admin.Models; +using Bit.Core.AdminConsole.Entities; -namespace Bit.Admin.Models; +namespace Bit.Admin.AdminConsole.Models; public class OrganizationsModel : PagedModel { diff --git a/src/Admin/Models/ProviderEditModel.cs b/src/Admin/AdminConsole/Models/ProviderEditModel.cs similarity index 96% rename from src/Admin/Models/ProviderEditModel.cs rename to src/Admin/AdminConsole/Models/ProviderEditModel.cs index b5219d8ed9..c00bdea6e5 100644 --- a/src/Admin/Models/ProviderEditModel.cs +++ b/src/Admin/AdminConsole/Models/ProviderEditModel.cs @@ -2,7 +2,7 @@ using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Models.Data.Provider; -namespace Bit.Admin.Models; +namespace Bit.Admin.AdminConsole.Models; public class ProviderEditModel : ProviderViewModel { diff --git a/src/Admin/Models/ProviderViewModel.cs b/src/Admin/AdminConsole/Models/ProviderViewModel.cs similarity index 95% rename from src/Admin/Models/ProviderViewModel.cs rename to src/Admin/AdminConsole/Models/ProviderViewModel.cs index 38e7eac4a2..9c4d07e8bf 100644 --- a/src/Admin/Models/ProviderViewModel.cs +++ b/src/Admin/AdminConsole/Models/ProviderViewModel.cs @@ -2,7 +2,7 @@ using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Models.Data.Provider; -namespace Bit.Admin.Models; +namespace Bit.Admin.AdminConsole.Models; public class ProviderViewModel { diff --git a/src/Admin/Models/ProvidersModel.cs b/src/Admin/AdminConsole/Models/ProvidersModel.cs similarity index 68% rename from src/Admin/Models/ProvidersModel.cs rename to src/Admin/AdminConsole/Models/ProvidersModel.cs index 5fb8e8dfab..6de815facf 100644 --- a/src/Admin/Models/ProvidersModel.cs +++ b/src/Admin/AdminConsole/Models/ProvidersModel.cs @@ -1,6 +1,7 @@ -using Bit.Core.AdminConsole.Entities.Provider; +using Bit.Admin.Models; +using Bit.Core.AdminConsole.Entities.Provider; -namespace Bit.Admin.Models; +namespace Bit.Admin.AdminConsole.Models; public class ProvidersModel : PagedModel { diff --git a/src/Admin/Views/Organizations/Connections.cshtml b/src/Admin/AdminConsole/Views/Organizations/Connections.cshtml similarity index 95% rename from src/Admin/Views/Organizations/Connections.cshtml rename to src/Admin/AdminConsole/Views/Organizations/Connections.cshtml index f1f8f4a8d3..6efdb34b20 100644 --- a/src/Admin/Views/Organizations/Connections.cshtml +++ b/src/Admin/AdminConsole/Views/Organizations/Connections.cshtml @@ -1,3 +1,4 @@ +@using Bit.Core.Enums @model OrganizationViewModel

Connections

@@ -49,7 +50,7 @@ } - @if(connection.Enabled) + @if(connection.Enabled) { @if(@TempData["ConnectionActivated"] != null && @TempData["ConnectionActivated"].ToString() == @Model.Organization.Id.ToString()) { @@ -62,8 +63,8 @@ { @if(connection.Type.Equals(OrganizationConnectionType.CloudBillingSync)) { - Manually Sync @@ -78,4 +79,4 @@
- \ No newline at end of file + diff --git a/src/Admin/Views/Organizations/Edit.cshtml b/src/Admin/AdminConsole/Views/Organizations/Edit.cshtml similarity index 95% rename from src/Admin/Views/Organizations/Edit.cshtml rename to src/Admin/AdminConsole/Views/Organizations/Edit.cshtml index ad4e4f8482..bed7c789ee 100644 --- a/src/Admin/Views/Organizations/Edit.cshtml +++ b/src/Admin/AdminConsole/Views/Organizations/Edit.cshtml @@ -1,4 +1,6 @@ @using Bit.Admin.Enums; +@using Bit.Admin.Models +@using Bit.Core.Enums @inject Bit.Admin.Services.IAccessControlService AccessControlService @model OrganizationEditModel @{ @@ -12,7 +14,7 @@ } @section Scripts { - @await Html.PartialAsync("_OrganizationFormScripts") + @await Html.PartialAsync("~/AdminConsole/Views/Shared/_OrganizationFormScripts.cshtml")