From c2975b003dd00a7a6917a5437615ebed52ff8722 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Mon, 15 Nov 2021 19:25:10 +1000 Subject: [PATCH 01/10] [Key Connector] Fix policy checks and other pre-reqs (#1711) * Require SSO Policy to enable Key Connector * Require that SSO is enabled to use Key Connector * Fix error messages "Key Connector" instead of "KeyConnector" * Refactor dependent policy checks to handle expansion * Block disabling Sso Policy if using Key Connector * Update tests for policies required by Key Connector * Fix tests * Add test for Key Connector to require Sso Policy * Add test: Sso config must be enabled to use Key Connector --- .../Services/Implementations/PolicyService.cs | 75 +++++++++++++------ .../Implementations/SsoConfigService.cs | 16 +++- test/Core.Test/Services/PolicyServiceTests.cs | 12 ++- .../Services/SsoConfigServiceTests.cs | 64 +++++++++++++++- 4 files changed, 134 insertions(+), 33 deletions(-) diff --git a/src/Core/Services/Implementations/PolicyService.cs b/src/Core/Services/Implementations/PolicyService.cs index c38ea9fde7..953317a0bf 100644 --- a/src/Core/Services/Implementations/PolicyService.cs +++ b/src/Core/Services/Implementations/PolicyService.cs @@ -54,37 +54,27 @@ namespace Bit.Core.Services case PolicyType.SingleOrg: if (!policy.Enabled) { - var requireSso = - await _policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.RequireSso); - if (requireSso?.Enabled == true) - { - throw new BadRequestException("Single Sign-On Authentication policy is enabled."); - } - - var vaultTimeout = - await _policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.MaximumVaultTimeout); - if (vaultTimeout?.Enabled == true) - { - throw new BadRequestException("Maximum Vault Timeout policy is enabled."); - } - - var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(org.Id); - if (ssoConfig?.GetData()?.UseKeyConnector == true) - { - throw new BadRequestException("KeyConnector is enabled."); - } + await RequiredBySsoAsync(org); + await RequiredByVaultTimeoutAsync(org); + await RequiredByKeyConnectorAsync(org); } break; case PolicyType.RequireSso: + if (policy.Enabled) + { + await DependsOnSingleOrgAsync(org); + } + else + { + await RequiredByKeyConnectorAsync(org); + } + break; + case PolicyType.MaximumVaultTimeout: if (policy.Enabled) { - var singleOrg = await _policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SingleOrg); - if (singleOrg?.Enabled != true) - { - throw new BadRequestException("Single Organization policy not enabled."); - } + await DependsOnSingleOrgAsync(org); } break; } @@ -144,5 +134,42 @@ namespace Bit.Core.Services await _policyRepository.UpsertAsync(policy); await _eventService.LogPolicyEventAsync(policy, Enums.EventType.Policy_Updated); } + + private async Task DependsOnSingleOrgAsync(Organization org) + { + var singleOrg = await _policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.SingleOrg); + if (singleOrg?.Enabled != true) + { + throw new BadRequestException("Single Organization policy not enabled."); + } + } + + private async Task RequiredBySsoAsync(Organization org) + { + var requireSso = await _policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.RequireSso); + if (requireSso?.Enabled == true) + { + throw new BadRequestException("Single Sign-On Authentication policy is enabled."); + } + } + + private async Task RequiredByKeyConnectorAsync(Organization org) + { + + var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(org.Id); + if (ssoConfig?.GetData()?.UseKeyConnector == true) + { + throw new BadRequestException("Key Connector is enabled."); + } + } + + private async Task RequiredByVaultTimeoutAsync(Organization org) + { + var vaultTimeout = await _policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.MaximumVaultTimeout); + if (vaultTimeout?.Enabled == true) + { + throw new BadRequestException("Maximum Vault Timeout policy is enabled."); + } + } } } diff --git a/src/Core/Services/Implementations/SsoConfigService.cs b/src/Core/Services/Implementations/SsoConfigService.cs index 429c470756..b97c338699 100644 --- a/src/Core/Services/Implementations/SsoConfigService.cs +++ b/src/Core/Services/Implementations/SsoConfigService.cs @@ -65,10 +65,20 @@ namespace Bit.Core.Services private async Task VerifyDependenciesAsync(SsoConfig config) { - var policy = await _policyRepository.GetByOrganizationIdTypeAsync(config.OrganizationId, PolicyType.SingleOrg); - if (policy is not { Enabled: true }) + var singleOrgPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(config.OrganizationId, PolicyType.SingleOrg); + if (singleOrgPolicy is not { Enabled: true }) { - throw new BadRequestException("KeyConnector requires Single Organization to be enabled."); + throw new BadRequestException("Key Connector requires the Single Organization policy to be enabled."); + } + + var ssoPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(config.OrganizationId, PolicyType.RequireSso); + if (ssoPolicy is not { Enabled: true }) + { + throw new BadRequestException("Key Connector requires the Single Sign-On Authentication policy to be enabled."); + } + + if (!config.Enabled) { + throw new BadRequestException("You must enable SSO to use Key Connector."); } } diff --git a/test/Core.Test/Services/PolicyServiceTests.cs b/test/Core.Test/Services/PolicyServiceTests.cs index 18d68e82b0..dc7705dc1a 100644 --- a/test/Core.Test/Services/PolicyServiceTests.cs +++ b/test/Core.Test/Services/PolicyServiceTests.cs @@ -126,12 +126,16 @@ namespace Bit.Core.Test.Services .UpsertAsync(default); } - [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task SaveAsync_SingleOrg_KeyConnectorEnabled_ThrowsBadRequest( - [PolicyFixtures.Policy(Enums.PolicyType.SingleOrg)] Core.Models.Table.Policy policy, + [Theory] + [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) }, Enums.PolicyType.SingleOrg)] + [InlineCustomAutoData(new[] { typeof(SutProviderCustomization) }, Enums.PolicyType.RequireSso)] + public async Task SaveAsync_PolicyRequiredByKeyConnector_DisablePolicy_ThrowsBadRequest( + Enums.PolicyType policyType, + Policy policy, SutProvider sutProvider) { policy.Enabled = false; + policy.Type = policyType; SetupOrg(sutProvider, policy.OrganizationId, new Organization { @@ -153,7 +157,7 @@ namespace Bit.Core.Test.Services Substitute.For(), Guid.NewGuid())); - Assert.Contains("KeyConnector is enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase); + Assert.Contains("Key Connector is enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase); await sutProvider.GetDependency() .DidNotReceiveWithAnyArgs() diff --git a/test/Core.Test/Services/SsoConfigServiceTests.cs b/test/Core.Test/Services/SsoConfigServiceTests.cs index 0db1864975..0205a1e760 100644 --- a/test/Core.Test/Services/SsoConfigServiceTests.cs +++ b/test/Core.Test/Services/SsoConfigServiceTests.cs @@ -145,7 +145,7 @@ namespace Bit.Core.Test.Services } [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task SaveAsync_KeyConnector_SingleOrgNotEnabled(SutProvider sutProvider) + public async Task SaveAsync_KeyConnector_SingleOrgNotEnabled_Throws(SutProvider sutProvider) { var utcNow = DateTime.UtcNow; @@ -162,7 +162,67 @@ namespace Bit.Core.Test.Services var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SaveAsync(ssoConfig)); - Assert.Contains("KeyConnector requires Single Organization to be enabled.", exception.Message); + Assert.Contains("Key Connector requires the Single Organization policy to be enabled.", exception.Message); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .UpsertAsync(default); + } + + [Theory, CustomAutoData(typeof(SutProviderCustomization))] + public async Task SaveAsync_KeyConnector_SsoPolicyNotEnabled_Throws(SutProvider sutProvider) + { + var utcNow = DateTime.UtcNow; + + var ssoConfig = new SsoConfig + { + Id = default, + Data = "{\"useKeyConnector\": true}", + Enabled = true, + OrganizationId = Guid.NewGuid(), + CreationDate = utcNow.AddDays(-10), + RevisionDate = utcNow.AddDays(-10), + }; + + sutProvider.GetDependency().GetByOrganizationIdTypeAsync( + Arg.Any(), Enums.PolicyType.SingleOrg).Returns(new Policy + { + Enabled = true + }); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.SaveAsync(ssoConfig)); + + Assert.Contains("Key Connector requires the Single Sign-On Authentication policy to be enabled.", exception.Message); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .UpsertAsync(default); + } + + [Theory, CustomAutoData(typeof(SutProviderCustomization))] + public async Task SaveAsync_KeyConnector_SsoConfigNotEnabled_Throws(SutProvider sutProvider) + { + var utcNow = DateTime.UtcNow; + + var ssoConfig = new SsoConfig + { + Id = default, + Data = "{\"useKeyConnector\": true}", + Enabled = false, + OrganizationId = Guid.NewGuid(), + CreationDate = utcNow.AddDays(-10), + RevisionDate = utcNow.AddDays(-10), + }; + + sutProvider.GetDependency().GetByOrganizationIdTypeAsync( + Arg.Any(), Arg.Any()).Returns(new Policy + { + Enabled = true + }); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.SaveAsync(ssoConfig)); + + Assert.Contains("You must enable SSO to use Key Connector.", exception.Message); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .UpsertAsync(default); From e3143271d7fd2d82fe2002fee3a2aab7b66135a6 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Mon, 15 Nov 2021 19:46:13 +1000 Subject: [PATCH 02/10] [Key Connector] Prevent user from leaving org (#1715) * Block user from leaving org using Key Connector * Add tests --- .../Controllers/OrganizationsController.cs | 6 ++ .../OrganizationsControllerTests.cs | 81 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 test/Api.Test/Controllers/OrganizationsControllerTests.cs diff --git a/src/Api/Controllers/OrganizationsController.cs b/src/Api/Controllers/OrganizationsController.cs index 3d90231373..74a59d021f 100644 --- a/src/Api/Controllers/OrganizationsController.cs +++ b/src/Api/Controllers/OrganizationsController.cs @@ -384,6 +384,12 @@ namespace Bit.Api.Controllers throw new NotFoundException(); } + var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(orgGuidId); + if (ssoConfig?.GetData()?.UseKeyConnector == true) + { + throw new BadRequestException("You cannot leave an Organization that is using Key Connector."); + } + var userId = _userService.GetProperUserId(User); await _organizationService.DeleteUserAsync(orgGuidId, userId.Value); } diff --git a/test/Api.Test/Controllers/OrganizationsControllerTests.cs b/test/Api.Test/Controllers/OrganizationsControllerTests.cs new file mode 100644 index 0000000000..9fa3b8ca6d --- /dev/null +++ b/test/Api.Test/Controllers/OrganizationsControllerTests.cs @@ -0,0 +1,81 @@ +using AutoFixture.Xunit2; +using Bit.Api.Controllers; +using Bit.Core.Context; +using Bit.Core.Exceptions; +using Bit.Core.Models.Table; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Settings; +using NSubstitute; +using System.Threading.Tasks; +using System.Security.Claims; +using System; +using Xunit; + +namespace Bit.Api.Test.Controllers +{ + public class OrganizationsControllerTests: IDisposable + { + private readonly GlobalSettings _globalSettings; + private readonly ICurrentContext _currentContext; + private readonly IOrganizationRepository _organizationRepository; + private readonly IOrganizationService _organizationService; + private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly IPaymentService _paymentService; + private readonly IPolicyRepository _policyRepository; + private readonly ISsoConfigRepository _ssoConfigRepository; + private readonly ISsoConfigService _ssoConfigService; + private readonly IUserService _userService; + + private readonly OrganizationsController _sut; + + public OrganizationsControllerTests() + { + _currentContext = Substitute.For(); + _globalSettings = Substitute.For(); + _organizationRepository = Substitute.For(); + _organizationService = Substitute.For(); + _organizationUserRepository = Substitute.For(); + _paymentService = Substitute.For(); + _policyRepository = Substitute.For(); + _ssoConfigRepository = Substitute.For(); + _ssoConfigService = Substitute.For(); + _userService = Substitute.For(); + + _sut = new OrganizationsController(_organizationRepository, _organizationUserRepository, + _policyRepository, _organizationService, _userService, _paymentService, _currentContext, + _ssoConfigRepository, _ssoConfigService, _globalSettings); + } + + public void Dispose() + { + _sut?.Dispose(); + } + + [Theory, AutoData] + public async Task OrganizationsController_WhenUserTriestoLeaveOrganizationUsingKeyConnector_Throws( + Guid orgId) + { + var ssoConfig = new SsoConfig + { + Id = default, + Data = "{\"useKeyConnector\": true}", + Enabled = true, + OrganizationId = orgId, + }; + + _currentContext.OrganizationUser(orgId).Returns(true); + _ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig); + _userService.GetProperUserId(Arg.Any()).Returns(new Guid()); + + var exception = await Assert.ThrowsAsync( + () => _sut.Leave(orgId.ToString())); + + Assert.Contains("You cannot leave an Organization that is using Key Connector.", + exception.Message); + + await _organizationService.DidNotReceiveWithAnyArgs().DeleteUserAsync(default, default); + } + } +} + From 27351762decdc7fb57262413eca9c262a4e48b67 Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Mon, 15 Nov 2021 13:23:51 -0500 Subject: [PATCH 03/10] Upload Docker images to AWS ECR Nonprod Repositories (#1713) --- .github/workflows/build.yml | 86 +++++++++++++++++++++++------------ .github/workflows/release.yml | 3 +- 2 files changed, 58 insertions(+), 31 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a4970654d1..15337f2bdf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -248,7 +248,9 @@ jobs: uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403 with: keyvault: "bitwarden-prod-kv" - secrets: "docker-password, + secrets: "aws-ecr-access-key-id, + aws-ecr-secret-access-key, + docker-password, docker-username, dct-delegate-2-repo-passphrase, dct-delegate-2-key" @@ -278,7 +280,6 @@ jobs: DCT_DELEGATE_KEY: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-key }} run: | mkdir -p ~/.docker/trust/private - echo "$DCT_DELEGATE_KEY" > ~/.docker/trust/private/$DCT_DELEGATION_KEY_ID.key - name: Setup service name @@ -306,34 +307,12 @@ jobs: run: | if [ "${{ matrix.service_name }}" = "K8S-Proxy" ]; then docker build -f ${{ matrix.base_path }}/Nginx/Dockerfile-k8s \ - -t ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }} ${{ matrix.base_path }}/Nginx + -t ${{ steps.setup.outputs.service_name }} ${{ matrix.base_path }}/Nginx else - docker build -t ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }} \ + docker build -t ${{ steps.setup.outputs.service_name }} \ ${{ matrix.base_path }}/${{ matrix.service_name }} fi - - name: Tag rc - if: github.ref == 'refs/heads/rc' - run: | - docker tag ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }} \ - ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }}:rc - - - name: Tag hotfix - if: github.ref == 'refs/heads/hotfix' - run: | - docker tag ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }} \ - ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }}:hotfix - - - name: Tag dev - if: github.ref == 'refs/heads/master' - run: | - docker tag ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }} \ - ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }}:dev - - - name: List Docker images - if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix' - run: docker images - - name: Docker Trust setup if: | matrix.docker_repo == 'bitwarden' @@ -342,27 +321,74 @@ jobs: DCT_REPO_PASSPHRASE: ${{ steps.retrieve-secrets.outputs.dct-delegate-2-repo-passphrase }} run: | echo "DOCKER_CONTENT_TRUST=1" >> $GITHUB_ENV - echo "DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE=$DCT_REPO_PASSPHRASE" >> $GITHUB_ENV + echo "DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE=$DCT_REPO_PASSPHRASE" >> $GITHUB_ENV - - name: Push rc images + - name: Tag and Push RC to Docker Hub if: github.ref == 'refs/heads/rc' run: | + docker tag ${{ steps.setup.outputs.service_name }} \ + ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }}:rc docker push ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }}:rc - - name: Push hotfix images + - name: Tag and Push Hotfix to Docker Hub if: github.ref == 'refs/heads/hotfix' run: | + docker tag ${{ steps.setup.outputs.service_name }} \ + ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }}:hotfix docker push ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }}:hotfix - - name: Push dev images + - name: Tag and Push Dev to Docker Hub if: github.ref == 'refs/heads/master' run: | + docker tag ${{ steps.setup.outputs.service_name }} \ + ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }}:dev docker push ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }}:dev - name: Log out of Docker if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix' run: docker logout + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@0d9a5be0dceea74e09396820e1e522ba4a110d2f # v1 + with: + aws-access-key-id: ${{ steps.retrieve-secrets.outputs.aws-ecr-access-key-id }} + aws-secret-access-key: ${{ steps.retrieve-secrets.outputs.aws-ecr-secret-access-key }} + aws-region: us-east-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@aaf69d68aa3fb14c1d5a6be9ac61fe15b48453a2 # v1 + + - name: Tag and Push RC to AWS ECR nonprod registry + if: github.ref == 'refs/heads/rc' + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ github.sha }} + run: | + docker tag ${{ steps.setup.outputs.service_name }} \ + $ECR_REGISTRY/nonprod/${{ steps.setup.outputs.service_name }}:rc-${IMAGE_TAG:(-8)} + docker push $ECR_REGISTRY/nonprod/${{ steps.setup.outputs.service_name }}:rc-${IMAGE_TAG:(-8)} + + - name: Tag and Push Hotfix to AWS ECR nonprod registry + if: github.ref == 'refs/heads/hotfix' + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ github.sha }} + run: | + docker tag ${{ steps.setup.outputs.service_name }} \ + $ECR_REGISTRY/nonprod/${{ steps.setup.outputs.service_name }}:hotfix-${IMAGE_TAG:(-8)} + docker push $ECR_REGISTRY/nonprod/${{ steps.setup.outputs.service_name }}:hotfix-${IMAGE_TAG:(-8)} + + - name: Tag and Push Dev to AWS ECR nonprod registry + if: github.ref == 'refs/heads/master' + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ github.sha }} + run: | + docker tag ${{ steps.setup.outputs.service_name }} \ + $ECR_REGISTRY/nonprod/${{ steps.setup.outputs.service_name }}:dev-${IMAGE_TAG:(-8)} + docker push $ECR_REGISTRY/nonprod/${{ steps.setup.outputs.service_name }}:dev-${IMAGE_TAG:(-8)} + upload: name: Upload diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 63a3a3888b..c56f642c71 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -117,7 +117,8 @@ jobs: release-docker: name: Build Docker images runs-on: ubuntu-20.04 - needs: setup + needs: + - setup env: _RELEASE_VERSION: ${{ needs.setup.outputs.release_version }} _BRANCH_NAME: ${{ needs.setup.outputs.branch-name }} From c660f8bf000982a5a0e5500189d28d434eff29f3 Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Mon, 15 Nov 2021 13:53:53 -0500 Subject: [PATCH 04/10] Fix Docker Notary (#1717) --- .github/workflows/build.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 15337f2bdf..6b16b9aa2c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -344,9 +344,11 @@ jobs: ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }}:dev docker push ${{ matrix.docker_repo }}/${{ steps.setup.outputs.service_name }}:dev - - name: Log out of Docker + - name: Log out of Docker and disable Docker Notary if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix' - run: docker logout + run: | + docker logout + echo "DOCKER_CONTENT_TRUST=0" >> $GITHUB_ENV - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@0d9a5be0dceea74e09396820e1e522ba4a110d2f # v1 From cdb622d4aa9e06407bb58549cafef32a3d16a0b5 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Tue, 16 Nov 2021 06:54:28 +1000 Subject: [PATCH 05/10] Add ApiUseKeyConnector flag to token response (#1710) --- src/Core/IdentityServer/CustomTokenRequestValidator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/IdentityServer/CustomTokenRequestValidator.cs b/src/Core/IdentityServer/CustomTokenRequestValidator.cs index 0b9eb8c613..bc113b82a0 100644 --- a/src/Core/IdentityServer/CustomTokenRequestValidator.cs +++ b/src/Core/IdentityServer/CustomTokenRequestValidator.cs @@ -95,7 +95,8 @@ namespace Bit.Core.IdentityServer if (context.Result.ValidatedRequest.GrantType == "client_credentials") { if (user.UsesKeyConnector) { - // KeyConnectorUrl is configured in the CLI client, just disable master password reset + // KeyConnectorUrl is configured in the CLI client, we just need to tell the client to use it + context.Result.CustomResponse["ApiUseKeyConnector"] = true; context.Result.CustomResponse["ResetMasterPassword"] = false; } return; From 3a22f91ff5d444485e80d55b8073d8f653035a16 Mon Sep 17 00:00:00 2001 From: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com> Date: Tue, 16 Nov 2021 09:52:02 -0800 Subject: [PATCH 06/10] Enable key connector selfhost (#1707) * initial commit * Add code for Key Connector feature * Add help URL to config * Fix folders for key-connector service * Fix paths for key-connector * fixing the env file builder when disabling the key connector * swapping a variable name Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> --- util/Setup/CertBuilder.cs | 14 ++++++++++++++ util/Setup/Configuration.cs | 3 +++ util/Setup/DockerComposeBuilder.cs | 2 ++ util/Setup/EnvironmentFileBuilder.cs | 24 ++++++++++++++++++++++++ util/Setup/NginxConfigBuilder.cs | 2 ++ util/Setup/Program.cs | 3 +++ util/Setup/Templates/DockerCompose.hbs | 16 ++++++++++++++++ util/Setup/Templates/NginxConfig.hbs | 6 ++++++ 8 files changed, 70 insertions(+) diff --git a/util/Setup/CertBuilder.cs b/util/Setup/CertBuilder.cs index 415b591480..143840e01a 100644 --- a/util/Setup/CertBuilder.cs +++ b/util/Setup/CertBuilder.cs @@ -97,5 +97,19 @@ namespace Bit.Setup Helpers.ShowBanner(_context, "WARNING", message, ConsoleColor.Yellow); } } + + public void BuildForUpdater() + { + if (_context.Config.EnableKeyConnector && !File.Exists("/bitwarden/key-connector/bwkc.pfx")) + { + Directory.CreateDirectory("/bitwarden/key-connector/"); + var keyConnectorCertPassword = Helpers.GetValueFromEnvFile("key-connector", + "keyConnectorSettings__certificate__filesystemPassword"); + Helpers.Exec("openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout bwkc.key " + + "-out bwkc.crt -subj \"/CN=Bitwarden Key Connector\" -days 36500"); + Helpers.Exec("openssl pkcs12 -export -out /bitwarden/key-connector/bwkc.pfx -inkey bwkc.key " + + $"-in bwkc.crt -passout pass:{keyConnectorCertPassword}"); + } + } } } diff --git a/util/Setup/Configuration.cs b/util/Setup/Configuration.cs index c816798ad8..2cb4edfc92 100644 --- a/util/Setup/Configuration.cs +++ b/util/Setup/Configuration.cs @@ -100,6 +100,9 @@ namespace Bit.Setup "Learn more: https://nginx.org/en/docs/http/ngx_http_realip_module.html")] public List RealIps { get; set; } + [Description("Enable Key Connector (https://bitwarden.com/help/article/deploy-key-connector)")] + public bool EnableKeyConnector { get; set; } = false; + [YamlIgnore] public string Domain { diff --git a/util/Setup/DockerComposeBuilder.cs b/util/Setup/DockerComposeBuilder.cs index 64afc8224f..b3394d8925 100644 --- a/util/Setup/DockerComposeBuilder.cs +++ b/util/Setup/DockerComposeBuilder.cs @@ -50,6 +50,7 @@ namespace Bit.Setup ComposeVersion = context.Config.ComposeVersion; } MssqlDataDockerVolume = context.Config.DatabaseDockerVolume; + EnableKeyConnector = context.Config.EnableKeyConnector; HttpPort = context.Config.HttpPort; HttpsPort = context.Config.HttpsPort; if (!string.IsNullOrWhiteSpace(context.CoreVersion)) @@ -64,6 +65,7 @@ namespace Bit.Setup public string ComposeVersion { get; set; } = "3"; public bool MssqlDataDockerVolume { get; set; } + public bool EnableKeyConnector { get; set; } public string HttpPort { get; set; } public string HttpsPort { get; set; } public bool HasPort => !string.IsNullOrWhiteSpace(HttpPort) || !string.IsNullOrWhiteSpace(HttpsPort); diff --git a/util/Setup/EnvironmentFileBuilder.cs b/util/Setup/EnvironmentFileBuilder.cs index 224c954086..ae27746d45 100644 --- a/util/Setup/EnvironmentFileBuilder.cs +++ b/util/Setup/EnvironmentFileBuilder.cs @@ -14,6 +14,7 @@ namespace Bit.Setup private IDictionary _mssqlValues; private IDictionary _globalOverrideValues; private IDictionary _mssqlOverrideValues; + private IDictionary _keyConnectorOverrideValues; public EnvironmentFileBuilder(Context context) { @@ -45,6 +46,7 @@ namespace Bit.Setup Init(); LoadExistingValues(_globalOverrideValues, "/bitwarden/env/global.override.env"); LoadExistingValues(_mssqlOverrideValues, "/bitwarden/env/mssql.override.env"); + LoadExistingValues(_keyConnectorOverrideValues, "/bitwarden/env/key-connector.override.env"); if (_context.Config.PushNotifications && _globalOverrideValues.ContainsKey("globalSettings__pushRelayBaseUri") && @@ -107,6 +109,18 @@ namespace Bit.Setup { ["SA_PASSWORD"] = dbPassword, }; + + _keyConnectorOverrideValues = new Dictionary + { + ["keyConnectorSettings__webVaultUri"] = _context.Config.Url, + ["keyConnectorSettings__identityServerUri"] = "http://identity:5000", + ["keyConnectorSettings__database__provider"] = "json", + ["keyConnectorSettings__database__jsonFilePath"] = "/etc/bitwarden/key-connector/data.json", + ["keyConnectorSettings__rsaKey__provider"] = "certificate", + ["keyConnectorSettings__certificate__provider"] = "filesystem", + ["keyConnectorSettings__certificate__filesystemPath"] = "/etc/bitwarden/key-connector/bwkc.pfx", + ["keyConnectorSettings__certificate__filesystemPassword"] = Helpers.SecureRandomString(32, alpha: true, numeric: true), + }; } private void LoadExistingValues(IDictionary _values, string file) @@ -179,6 +193,16 @@ namespace Bit.Setup } Helpers.Exec("chmod 600 /bitwarden/env/mssql.override.env"); + if (_context.Config.EnableKeyConnector) + { + using (var sw = File.CreateText("/bitwarden/env/key-connector.override.env")) + { + sw.Write(template(new TemplateModel(_keyConnectorOverrideValues))); + } + + Helpers.Exec("chmod 600 /bitwarden/env/key-connector.override.env"); + } + // Empty uid env file. Only used on Linux hosts. if (!File.Exists("/bitwarden/env/uid.env")) { diff --git a/util/Setup/NginxConfigBuilder.cs b/util/Setup/NginxConfigBuilder.cs index 1cf5a90bc2..07ec52a581 100644 --- a/util/Setup/NginxConfigBuilder.cs +++ b/util/Setup/NginxConfigBuilder.cs @@ -70,6 +70,7 @@ namespace Bit.Setup { Captcha = context.Config.Captcha; Ssl = context.Config.Ssl; + EnableKeyConnector = context.Config.EnableKeyConnector; Domain = context.Config.Domain; Url = context.Config.Url; RealIps = context.Config.RealIps; @@ -117,6 +118,7 @@ namespace Bit.Setup public bool Captcha { get; set; } public bool Ssl { get; set; } + public bool EnableKeyConnector { get; set; } public string Domain { get; set; } public string Url { get; set; } public string CertificatePath { get; set; } diff --git a/util/Setup/Program.cs b/util/Setup/Program.cs index 39ee5126bc..fa8a4e2f9a 100644 --- a/util/Setup/Program.cs +++ b/util/Setup/Program.cs @@ -291,6 +291,9 @@ namespace Bit.Setup var environmentFileBuilder = new EnvironmentFileBuilder(_context); environmentFileBuilder.BuildForUpdater(); + + var certBuilder = new CertBuilder(_context); + certBuilder.BuildForUpdater(); var nginxBuilder = new NginxConfigBuilder(_context); nginxBuilder.BuildForUpdater(); diff --git a/util/Setup/Templates/DockerCompose.hbs b/util/Setup/Templates/DockerCompose.hbs index 0e5cbb6fc7..82a65dc32f 100644 --- a/util/Setup/Templates/DockerCompose.hbs +++ b/util/Setup/Templates/DockerCompose.hbs @@ -194,6 +194,22 @@ services: networks: - default - public + +{{#if EnableKeyConnector}} + key-connector: + image: bitwarden/key-connector:latest + container_name: bitwarden-key-connector + restart: always + volumes: + - ../key-connector:/etc/bitwarden/key-connector + - ../ca-certificates:/etc/bitwarden/ca-certificates + - ../logs/key-connector:/etc/bitwarden/logs + env_file: + - ../env/key-connector.override.env + networks: + - default + - public +{{/if}} {{#if MssqlDataDockerVolume}} volumes: diff --git a/util/Setup/Templates/NginxConfig.hbs b/util/Setup/Templates/NginxConfig.hbs index 48de67526d..7bc964635a 100644 --- a/util/Setup/Templates/NginxConfig.hbs +++ b/util/Setup/Templates/NginxConfig.hbs @@ -166,4 +166,10 @@ server { include /etc/nginx/security-headers.conf; add_header X-Frame-Options SAMEORIGIN; } + +{{#if EnableKeyConnector}} + location /key-connector/ { + proxy_pass http://key-connector:5000/; + } +{{/if}} } From f866b25e43ed07a70bb76f62c74502f592c18f7e Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Wed, 17 Nov 2021 11:46:35 +0100 Subject: [PATCH 07/10] Key Connector feature toggle (#1716) --- src/Admin/Models/OrganizationEditModel.cs | 4 + src/Admin/Views/Organizations/Edit.cshtml | 4 + .../Controllers/OrganizationsController.cs | 4 +- .../CustomTokenRequestValidator.cs | 2 +- .../OrganizationSsoRequestModel.cs | 4 +- .../Api/Response/OrganizationResponseModel.cs | 2 + .../ProfileOrganizationResponseModel.cs | 6 +- ...rofileProviderOrganizationResponseModel.cs | 1 + .../Models/Business/OrganizationLicense.cs | 28 +- src/Core/Models/Data/OrganizationAbility.cs | 2 + .../OrganizationUserOrganizationDetails.cs | 1 + .../ProviderUserOrganizationDetails.cs | 1 + src/Core/Models/Data/SsoConfigurationData.cs | 2 +- src/Core/Models/StaticStore/Plan.cs | 1 + src/Core/Models/Table/Organization.cs | 1 + .../EntityFramework/OrganizationRepository.cs | 1 + ...izationUserOrganizationDetailsViewQuery.cs | 1 + ...roviderUserOrganizationDetailsViewQuery.cs | 1 + src/Core/Services/ISsoConfigService.cs | 2 +- .../Implementations/OrganizationService.cs | 25 +- .../Services/Implementations/PolicyService.cs | 2 +- .../Implementations/SsoConfigService.cs | 21 +- .../Services/Implementations/UserService.cs | 8 +- src/Core/Utilities/StaticStore.cs | 2 + .../Stored Procedures/Organization_Create.sql | 9 +- .../Organization_ReadAbilities.sql | 1 + .../Stored Procedures/Organization_Update.sql | 6 +- src/Sql/dbo/Tables/Organization.sql | 1 + ...rganizationUserOrganizationDetailsView.sql | 1 + ...derUserProviderOrganizationDetailsView.sql | 1 + .../OrganizationsControllerTests.cs | 6 +- .../EqualityComparers/OrganizationCompare.cs | 1 + .../Services/OrganizationServiceTests.cs | 2 +- test/Core.Test/Services/PolicyServiceTests.cs | 2 +- .../Services/SsoConfigServiceTests.cs | 153 +- .../2021-11-12_00_KeyConnectorFlag.sql | 420 +++++ ...0211115145402_KeyConnectorFlag.Designer.cs | 1498 ++++++++++++++++ .../20211115145402_KeyConnectorFlag.cs | 24 + .../DatabaseContextModelSnapshot.cs | 3 + util/MySqlMigrations/MySqlMigrations.csproj | 1 + .../2021-11-12_00_KeyConnectorFlag.sql | Bin 0 -> 492 bytes ...0211115142623_KeyConnectorFlag.Designer.cs | 1507 +++++++++++++++++ .../20211115142623_KeyConnectorFlag.cs | 24 + .../DatabaseContextModelSnapshot.cs | 3 + .../2021-11-12_00_KeyConnectorFlag.psql | Bin 0 -> 486 bytes 45 files changed, 3720 insertions(+), 69 deletions(-) create mode 100644 util/Migrator/DbScripts/2021-11-12_00_KeyConnectorFlag.sql create mode 100644 util/MySqlMigrations/Migrations/20211115145402_KeyConnectorFlag.Designer.cs create mode 100644 util/MySqlMigrations/Migrations/20211115145402_KeyConnectorFlag.cs create mode 100644 util/MySqlMigrations/Scripts/2021-11-12_00_KeyConnectorFlag.sql create mode 100644 util/PostgresMigrations/Migrations/20211115142623_KeyConnectorFlag.Designer.cs create mode 100644 util/PostgresMigrations/Migrations/20211115142623_KeyConnectorFlag.cs create mode 100644 util/PostgresMigrations/Scripts/2021-11-12_00_KeyConnectorFlag.psql diff --git a/src/Admin/Models/OrganizationEditModel.cs b/src/Admin/Models/OrganizationEditModel.cs index 30b6f897d2..fe9c445cb5 100644 --- a/src/Admin/Models/OrganizationEditModel.cs +++ b/src/Admin/Models/OrganizationEditModel.cs @@ -32,6 +32,7 @@ namespace Bit.Admin.Models MaxCollections = org.MaxCollections; UsePolicies = org.UsePolicies; UseSso = org.UseSso; + UseKeyConnector = org.UseKeyConnector; UseGroups = org.UseGroups; UseDirectory = org.UseDirectory; UseEvents = org.UseEvents; @@ -78,6 +79,8 @@ namespace Bit.Admin.Models public bool UsePolicies { get; set; } [Display(Name = "SSO")] public bool UseSso { get; set; } + [Display(Name = "Key Connector with Customer Encryption")] + public bool UseKeyConnector { get; set; } [Display(Name = "Groups")] public bool UseGroups { get; set; } [Display(Name = "Directory")] @@ -123,6 +126,7 @@ namespace Bit.Admin.Models existingOrganization.MaxCollections = MaxCollections; existingOrganization.UsePolicies = UsePolicies; existingOrganization.UseSso = UseSso; + existingOrganization.UseKeyConnector = UseKeyConnector; existingOrganization.UseGroups = UseGroups; existingOrganization.UseDirectory = UseDirectory; existingOrganization.UseEvents = UseEvents; diff --git a/src/Admin/Views/Organizations/Edit.cshtml b/src/Admin/Views/Organizations/Edit.cshtml index bc1c7ad571..c154ea2457 100644 --- a/src/Admin/Views/Organizations/Edit.cshtml +++ b/src/Admin/Views/Organizations/Edit.cshtml @@ -215,6 +215,10 @@ +
+ + +
diff --git a/src/Api/Controllers/OrganizationsController.cs b/src/Api/Controllers/OrganizationsController.cs index 74a59d021f..eb977568fc 100644 --- a/src/Api/Controllers/OrganizationsController.cs +++ b/src/Api/Controllers/OrganizationsController.cs @@ -385,7 +385,7 @@ namespace Bit.Api.Controllers } var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(orgGuidId); - if (ssoConfig?.GetData()?.UseKeyConnector == true) + if (ssoConfig?.GetData()?.KeyConnectorEnabled == true) { throw new BadRequestException("You cannot leave an Organization that is using Key Connector."); } @@ -648,7 +648,7 @@ namespace Bit.Api.Controllers var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(id); ssoConfig = ssoConfig == null ? model.ToSsoConfig(id) : model.ToSsoConfig(ssoConfig); - await _ssoConfigService.SaveAsync(ssoConfig); + await _ssoConfigService.SaveAsync(ssoConfig, organization); return new OrganizationSsoResponseModel(organization, _globalSettings, ssoConfig); } diff --git a/src/Core/IdentityServer/CustomTokenRequestValidator.cs b/src/Core/IdentityServer/CustomTokenRequestValidator.cs index bc113b82a0..876706ce96 100644 --- a/src/Core/IdentityServer/CustomTokenRequestValidator.cs +++ b/src/Core/IdentityServer/CustomTokenRequestValidator.cs @@ -111,7 +111,7 @@ namespace Bit.Core.IdentityServer var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organizationId); var ssoConfigData = ssoConfig.GetData(); - if (ssoConfigData is { UseKeyConnector: true } && !string.IsNullOrEmpty(ssoConfigData.KeyConnectorUrl)) + if (ssoConfigData is { KeyConnectorEnabled: true } && !string.IsNullOrEmpty(ssoConfigData.KeyConnectorUrl)) { context.Result.CustomResponse["KeyConnectorUrl"] = ssoConfigData.KeyConnectorUrl; // Prevent clients redirecting to set-password diff --git a/src/Core/Models/Api/Request/Organizations/OrganizationSsoRequestModel.cs b/src/Core/Models/Api/Request/Organizations/OrganizationSsoRequestModel.cs index f87ddfdf7d..c52740c5b0 100644 --- a/src/Core/Models/Api/Request/Organizations/OrganizationSsoRequestModel.cs +++ b/src/Core/Models/Api/Request/Organizations/OrganizationSsoRequestModel.cs @@ -42,7 +42,7 @@ namespace Bit.Core.Models.Api [Required] public SsoType ConfigType { get; set; } - public bool UseKeyConnector { get; set; } + public bool KeyConnectorEnabled { get; set; } public string KeyConnectorUrl { get; set; } // OIDC @@ -178,7 +178,7 @@ namespace Bit.Core.Models.Api return new SsoConfigurationData { ConfigType = ConfigType, - UseKeyConnector = UseKeyConnector, + KeyConnectorEnabled = KeyConnectorEnabled, KeyConnectorUrl = KeyConnectorUrl, Authority = Authority, ClientId = ClientId, diff --git a/src/Core/Models/Api/Response/OrganizationResponseModel.cs b/src/Core/Models/Api/Response/OrganizationResponseModel.cs index 7454a37d4a..bee98103ef 100644 --- a/src/Core/Models/Api/Response/OrganizationResponseModel.cs +++ b/src/Core/Models/Api/Response/OrganizationResponseModel.cs @@ -35,6 +35,7 @@ namespace Bit.Core.Models.Api MaxStorageGb = organization.MaxStorageGb; UsePolicies = organization.UsePolicies; UseSso = organization.UseSso; + UseKeyConnector = organization.UseKeyConnector; UseGroups = organization.UseGroups; UseDirectory = organization.UseDirectory; UseEvents = organization.UseEvents; @@ -65,6 +66,7 @@ namespace Bit.Core.Models.Api public short? MaxStorageGb { get; set; } public bool UsePolicies { get; set; } public bool UseSso { get; set; } + public bool UseKeyConnector { get; set; } public bool UseGroups { get; set; } public bool UseDirectory { get; set; } public bool UseEvents { get; set; } diff --git a/src/Core/Models/Api/Response/ProfileOrganizationResponseModel.cs b/src/Core/Models/Api/Response/ProfileOrganizationResponseModel.cs index 427873b653..b4476c3afd 100644 --- a/src/Core/Models/Api/Response/ProfileOrganizationResponseModel.cs +++ b/src/Core/Models/Api/Response/ProfileOrganizationResponseModel.cs @@ -14,6 +14,7 @@ namespace Bit.Core.Models.Api Name = organization.Name; UsePolicies = organization.UsePolicies; UseSso = organization.UseSso; + UseKeyConnector = organization.UseKeyConnector; UseGroups = organization.UseGroups; UseDirectory = organization.UseDirectory; UseEvents = organization.UseEvents; @@ -41,7 +42,7 @@ namespace Bit.Core.Models.Api if (organization.SsoConfig != null) { var ssoConfigData = SsoConfigurationData.Deserialize(organization.SsoConfig); - UsesKeyConnector = ssoConfigData.UseKeyConnector && !string.IsNullOrEmpty(ssoConfigData.KeyConnectorUrl); + KeyConnectorEnabled = ssoConfigData.KeyConnectorEnabled && !string.IsNullOrEmpty(ssoConfigData.KeyConnectorUrl); KeyConnectorUrl = ssoConfigData.KeyConnectorUrl; } } @@ -50,6 +51,7 @@ namespace Bit.Core.Models.Api public string Name { get; set; } public bool UsePolicies { get; set; } public bool UseSso { get; set; } + public bool UseKeyConnector { get; set; } public bool UseGroups { get; set; } public bool UseDirectory { get; set; } public bool UseEvents { get; set; } @@ -74,7 +76,7 @@ namespace Bit.Core.Models.Api public bool HasPublicAndPrivateKeys { get; set; } public string ProviderId { get; set; } public string ProviderName { get; set; } - public bool UsesKeyConnector { get; set; } + public bool KeyConnectorEnabled { get; set; } public string KeyConnectorUrl { get; set; } } } diff --git a/src/Core/Models/Api/Response/ProfileProviderOrganizationResponseModel.cs b/src/Core/Models/Api/Response/ProfileProviderOrganizationResponseModel.cs index c877fc6352..f760c78454 100644 --- a/src/Core/Models/Api/Response/ProfileProviderOrganizationResponseModel.cs +++ b/src/Core/Models/Api/Response/ProfileProviderOrganizationResponseModel.cs @@ -12,6 +12,7 @@ namespace Bit.Core.Models.Api Name = organization.Name; UsePolicies = organization.UsePolicies; UseSso = organization.UseSso; + UseKeyConnector = organization.UseKeyConnector; UseGroups = organization.UseGroups; UseDirectory = organization.UseDirectory; UseEvents = organization.UseEvents; diff --git a/src/Core/Models/Business/OrganizationLicense.cs b/src/Core/Models/Business/OrganizationLicense.cs index cd68527e3d..d493855e6d 100644 --- a/src/Core/Models/Business/OrganizationLicense.cs +++ b/src/Core/Models/Business/OrganizationLicense.cs @@ -20,7 +20,7 @@ namespace Bit.Core.Models.Business public OrganizationLicense(Organization org, SubscriptionInfo subscriptionInfo, Guid installationId, ILicensingService licenseService, int? version = null) { - Version = version.GetValueOrDefault(7); // TODO: bump to version 8 + Version = version.GetValueOrDefault(CURRENT_LICENSE_FILE_VERSION); // TODO: Remember to change the constant LicenseKey = org.LicenseKey; InstallationId = installationId; Id = org.Id; @@ -34,6 +34,7 @@ namespace Bit.Core.Models.Business MaxCollections = org.MaxCollections; UsePolicies = org.UsePolicies; UseSso = org.UseSso; + UseKeyConnector = org.UseKeyConnector; UseGroups = org.UseGroups; UseEvents = org.UseEvents; UseDirectory = org.UseDirectory; @@ -104,6 +105,7 @@ namespace Bit.Core.Models.Business public short? MaxCollections { get; set; } public bool UsePolicies { get; set; } public bool UseSso { get; set; } + public bool UseKeyConnector { get; set; } public bool UseGroups { get; set; } public bool UseEvents { get; set; } public bool UseDirectory { get; set; } @@ -124,10 +126,19 @@ namespace Bit.Core.Models.Business [JsonIgnore] public byte[] SignatureBytes => Convert.FromBase64String(Signature); + /// + /// Represents the current version of the license format. Should be updated whenever new fields are added. + /// + private const int CURRENT_LICENSE_FILE_VERSION = 8; + private bool ValidLicenseVersion + { + get => Version is >= 1 and <= 9; + } + public byte[] GetDataBytes(bool forHash = false) { string data = null; - if (Version >= 1 && Version <= 8) + if (ValidLicenseVersion) { var props = typeof(OrganizationLicense) .GetProperties(BindingFlags.Public | BindingFlags.Instance) @@ -148,6 +159,8 @@ namespace Bit.Core.Models.Business (Version >= 7 || !p.Name.Equals(nameof(UseSso))) && // UseResetPassword was added in Version 8 (Version >= 8 || !p.Name.Equals(nameof(UseResetPassword))) && + // UseKeyConnector was added in Version 9 + (Version >= 9 || !p.Name.Equals(nameof(UseKeyConnector))) && ( !forHash || ( @@ -184,7 +197,7 @@ namespace Bit.Core.Models.Business return false; } - if (Version >= 1 && Version <= 8) + if (ValidLicenseVersion) { return InstallationId == globalSettings.Installation.Id && SelfHost; } @@ -201,7 +214,7 @@ namespace Bit.Core.Models.Business return false; } - if (Version >= 1 && Version <= 8) + if (ValidLicenseVersion) { var valid = globalSettings.Installation.Id == InstallationId && @@ -245,12 +258,17 @@ namespace Bit.Core.Models.Business { valid = organization.UseSso == UseSso; } - + if (valid && Version >= 8) { valid = organization.UseResetPassword == UseResetPassword; } + if (valid && Version >= 9) + { + valid = organization.UseKeyConnector == UseKeyConnector; + } + return valid; } else diff --git a/src/Core/Models/Data/OrganizationAbility.cs b/src/Core/Models/Data/OrganizationAbility.cs index 0433f38805..e02ab6a18c 100644 --- a/src/Core/Models/Data/OrganizationAbility.cs +++ b/src/Core/Models/Data/OrganizationAbility.cs @@ -17,6 +17,7 @@ namespace Bit.Core.Models.Data UsersGetPremium = organization.UsersGetPremium; Enabled = organization.Enabled; UseSso = organization.UseSso; + UseKeyConnector = organization.UseKeyConnector; UseResetPassword = organization.UseResetPassword; } @@ -27,6 +28,7 @@ namespace Bit.Core.Models.Data public bool UsersGetPremium { get; set; } public bool Enabled { get; set; } public bool UseSso { get; set; } + public bool UseKeyConnector { get; set; } public bool UseResetPassword { get; set; } } } diff --git a/src/Core/Models/Data/OrganizationUserOrganizationDetails.cs b/src/Core/Models/Data/OrganizationUserOrganizationDetails.cs index 85f1cc3968..06a3ba5123 100644 --- a/src/Core/Models/Data/OrganizationUserOrganizationDetails.cs +++ b/src/Core/Models/Data/OrganizationUserOrganizationDetails.cs @@ -9,6 +9,7 @@ namespace Bit.Core.Models.Data public string Name { get; set; } public bool UsePolicies { get; set; } public bool UseSso { get; set; } + public bool UseKeyConnector { get; set; } public bool UseGroups { get; set; } public bool UseDirectory { get; set; } public bool UseEvents { get; set; } diff --git a/src/Core/Models/Data/Provider/ProviderUserOrganizationDetails.cs b/src/Core/Models/Data/Provider/ProviderUserOrganizationDetails.cs index 135617ddd4..38c5bb2106 100644 --- a/src/Core/Models/Data/Provider/ProviderUserOrganizationDetails.cs +++ b/src/Core/Models/Data/Provider/ProviderUserOrganizationDetails.cs @@ -10,6 +10,7 @@ namespace Bit.Core.Models.Data public string Name { get; set; } public bool UsePolicies { get; set; } public bool UseSso { get; set; } + public bool UseKeyConnector { get; set; } public bool UseGroups { get; set; } public bool UseDirectory { get; set; } public bool UseEvents { get; set; } diff --git a/src/Core/Models/Data/SsoConfigurationData.cs b/src/Core/Models/Data/SsoConfigurationData.cs index a7f2e3f99a..b8745e6049 100644 --- a/src/Core/Models/Data/SsoConfigurationData.cs +++ b/src/Core/Models/Data/SsoConfigurationData.cs @@ -27,7 +27,7 @@ namespace Bit.Core.Models.Data public SsoType ConfigType { get; set; } - public bool UseKeyConnector { get; set; } + public bool KeyConnectorEnabled { get; set; } public string KeyConnectorUrl { get; set; } // OIDC diff --git a/src/Core/Models/StaticStore/Plan.cs b/src/Core/Models/StaticStore/Plan.cs index b03e35d4cd..2bed65c7fe 100644 --- a/src/Core/Models/StaticStore/Plan.cs +++ b/src/Core/Models/StaticStore/Plan.cs @@ -33,6 +33,7 @@ namespace Bit.Core.Models.StaticStore public bool Has2fa { get; set; } public bool HasApi { get; set; } public bool HasSso { get; set; } + public bool HasKeyConnector { get; set; } public bool HasResetPassword { get; set; } public bool UsersGetPremium { get; set; } diff --git a/src/Core/Models/Table/Organization.cs b/src/Core/Models/Table/Organization.cs index de65fbc83b..644017bac6 100644 --- a/src/Core/Models/Table/Organization.cs +++ b/src/Core/Models/Table/Organization.cs @@ -38,6 +38,7 @@ namespace Bit.Core.Models.Table public short? MaxCollections { get; set; } public bool UsePolicies { get; set; } public bool UseSso { get; set; } + public bool UseKeyConnector { get; set; } public bool UseGroups { get; set; } public bool UseDirectory { get; set; } public bool UseEvents { get; set; } diff --git a/src/Core/Repositories/EntityFramework/OrganizationRepository.cs b/src/Core/Repositories/EntityFramework/OrganizationRepository.cs index 2f7c5781d5..d6eb20cc08 100644 --- a/src/Core/Repositories/EntityFramework/OrganizationRepository.cs +++ b/src/Core/Repositories/EntityFramework/OrganizationRepository.cs @@ -87,6 +87,7 @@ namespace Bit.Core.Repositories.EntityFramework UsersGetPremium = e.UsersGetPremium, Using2fa = e.Use2fa && e.TwoFactorProviders != null, UseSso = e.UseSso, + UseKeyConnector = e.UseKeyConnector, }).ToListAsync(); } } diff --git a/src/Core/Repositories/EntityFramework/Queries/OrganizationUserOrganizationDetailsViewQuery.cs b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserOrganizationDetailsViewQuery.cs index 293a8dfb86..d48f0cfc69 100644 --- a/src/Core/Repositories/EntityFramework/Queries/OrganizationUserOrganizationDetailsViewQuery.cs +++ b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserOrganizationDetailsViewQuery.cs @@ -29,6 +29,7 @@ namespace Bit.Core.Repositories.EntityFramework.Queries Enabled = x.o.Enabled, UsePolicies = x.o.UsePolicies, UseSso = x.o.UseSso, + UseKeyConnector = x.o.UseKeyConnector, UseGroups = x.o.UseGroups, UseDirectory = x.o.UseDirectory, UseEvents = x.o.UseEvents, diff --git a/src/Core/Repositories/EntityFramework/Queries/ProviderUserOrganizationDetailsViewQuery.cs b/src/Core/Repositories/EntityFramework/Queries/ProviderUserOrganizationDetailsViewQuery.cs index 436f8ee339..c21c68fcc5 100644 --- a/src/Core/Repositories/EntityFramework/Queries/ProviderUserOrganizationDetailsViewQuery.cs +++ b/src/Core/Repositories/EntityFramework/Queries/ProviderUserOrganizationDetailsViewQuery.cs @@ -21,6 +21,7 @@ namespace Bit.Core.Repositories.EntityFramework.Queries Enabled = x.o.Enabled, UsePolicies = x.o.UsePolicies, UseSso = x.o.UseSso, + UseKeyConnector = x.o.UseKeyConnector, UseGroups = x.o.UseGroups, UseDirectory = x.o.UseDirectory, UseEvents = x.o.UseEvents, diff --git a/src/Core/Services/ISsoConfigService.cs b/src/Core/Services/ISsoConfigService.cs index 9154dcccab..3c1d06d755 100644 --- a/src/Core/Services/ISsoConfigService.cs +++ b/src/Core/Services/ISsoConfigService.cs @@ -5,6 +5,6 @@ namespace Bit.Core.Services { public interface ISsoConfigService { - Task SaveAsync(SsoConfig config); + Task SaveAsync(SsoConfig config, Organization organization); } } diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index b775eb4bf7..b222fe724d 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -247,6 +247,16 @@ namespace Bit.Core.Services } } + if (!newPlan.HasKeyConnector && organization.UseKeyConnector) + { + var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id); + if (ssoConfig != null && ssoConfig.GetData().KeyConnectorEnabled) + { + throw new BadRequestException("Your new plan does not allow the Key Connector feature. " + + "Disable your Key Connector."); + } + } + if (!newPlan.HasResetPassword && organization.UseResetPassword) { var resetPasswordPolicy = @@ -295,6 +305,7 @@ namespace Bit.Core.Services organization.Use2fa = newPlan.Has2fa; organization.UseApi = newPlan.HasApi; organization.UseSso = newPlan.HasSso; + organization.UseKeyConnector = newPlan.HasKeyConnector; organization.UseResetPassword = newPlan.HasResetPassword; organization.SelfHost = newPlan.HasSelfHost; organization.UsersGetPremium = newPlan.UsersGetPremium || upgrade.PremiumAccessAddon; @@ -687,6 +698,7 @@ namespace Bit.Core.Services MaxStorageGb = _globalSettings.SelfHosted ? 10240 : license.MaxStorageGb, // 10 TB UsePolicies = license.UsePolicies, UseSso = license.UseSso, + UseKeyConnector = license.UseKeyConnector, UseGroups = license.UseGroups, UseDirectory = license.UseDirectory, UseEvents = license.UseEvents, @@ -865,6 +877,16 @@ namespace Bit.Core.Services } } + if (!license.UseKeyConnector && organization.UseKeyConnector) + { + var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id); + if (ssoConfig != null && ssoConfig.GetData().KeyConnectorEnabled) + { + throw new BadRequestException($"Your organization currently has Key Connector enabled. " + + $"Your new license does not allow for the use of Key Connector. Disable your Key Connector."); + } + } + if (!license.UseResetPassword && organization.UseResetPassword) { var resetPasswordPolicy = @@ -895,6 +917,7 @@ namespace Bit.Core.Services organization.UseApi = license.UseApi; organization.UsePolicies = license.UsePolicies; organization.UseSso = license.UseSso; + organization.UseKeyConnector = license.UseKeyConnector; organization.UseResetPassword = license.UseResetPassword; organization.SelfHost = license.SelfHost; organization.UsersGetPremium = license.UsersGetPremium; @@ -2141,7 +2164,7 @@ namespace Bit.Core.Services private async Task ValidateDeleteOrganizationAsync(Organization organization) { var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id); - if (ssoConfig?.GetData()?.UseKeyConnector == true) + if (ssoConfig?.GetData()?.KeyConnectorEnabled == true) { throw new BadRequestException("You cannot delete an Organization that is using Key Connector."); } diff --git a/src/Core/Services/Implementations/PolicyService.cs b/src/Core/Services/Implementations/PolicyService.cs index 953317a0bf..8f7fbf1302 100644 --- a/src/Core/Services/Implementations/PolicyService.cs +++ b/src/Core/Services/Implementations/PolicyService.cs @@ -157,7 +157,7 @@ namespace Bit.Core.Services { var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(org.Id); - if (ssoConfig?.GetData()?.UseKeyConnector == true) + if (ssoConfig?.GetData()?.KeyConnectorEnabled == true) { throw new BadRequestException("Key Connector is enabled."); } diff --git a/src/Core/Services/Implementations/SsoConfigService.cs b/src/Core/Services/Implementations/SsoConfigService.cs index b97c338699..40fe6a87e1 100644 --- a/src/Core/Services/Implementations/SsoConfigService.cs +++ b/src/Core/Services/Implementations/SsoConfigService.cs @@ -30,7 +30,7 @@ namespace Bit.Core.Services _eventService = eventService; } - public async Task SaveAsync(SsoConfig config) + public async Task SaveAsync(SsoConfig config, Organization organization) { var now = DateTime.UtcNow; config.RevisionDate = now; @@ -39,14 +39,14 @@ namespace Bit.Core.Services config.CreationDate = now; } - var useKeyConnector = config.GetData().UseKeyConnector; + var useKeyConnector = config.GetData().KeyConnectorEnabled; if (useKeyConnector) { - await VerifyDependenciesAsync(config); + await VerifyDependenciesAsync(config, organization); } var oldConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(config.OrganizationId); - var disabledKeyConnector = oldConfig?.GetData()?.UseKeyConnector == true && !useKeyConnector; + var disabledKeyConnector = oldConfig?.GetData()?.KeyConnectorEnabled == true && !useKeyConnector; if (disabledKeyConnector && await AnyOrgUserHasKeyConnectorEnabledAsync(config.OrganizationId)) { throw new BadRequestException("Key Connector cannot be disabled at this moment."); @@ -63,8 +63,13 @@ namespace Bit.Core.Services return userDetails.Any(u => u.UsesKeyConnector); } - private async Task VerifyDependenciesAsync(SsoConfig config) + private async Task VerifyDependenciesAsync(SsoConfig config, Organization organization) { + if (!organization.UseKeyConnector) + { + throw new BadRequestException("Organization cannot use Key Connector."); + } + var singleOrgPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(config.OrganizationId, PolicyType.SingleOrg); if (singleOrgPolicy is not { Enabled: true }) { @@ -91,10 +96,10 @@ namespace Bit.Core.Services await _eventService.LogOrganizationEventAsync(organization, e); } - var useKeyConnector = config.GetData().UseKeyConnector; - if (oldConfig?.GetData()?.UseKeyConnector != useKeyConnector) + var keyConnectorEnabled = config.GetData().KeyConnectorEnabled; + if (oldConfig?.GetData()?.KeyConnectorEnabled != keyConnectorEnabled) { - var e = useKeyConnector + var e = keyConnectorEnabled ? EventType.Organization_EnabledKeyConnector : EventType.Organization_DisabledKeyConnector; await _eventService.LogOrganizationEventAsync(organization, e); diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 215e895858..a5b30de095 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -646,7 +646,7 @@ namespace Bit.Core.Services if (user.UsesKeyConnector) { - Logger.LogWarning("Already uses key connector."); + Logger.LogWarning("Already uses Key Connector."); return IdentityResult.Failed(_identityErrorDescriber.UserAlreadyHasPassword()); } @@ -671,7 +671,7 @@ namespace Bit.Core.Services if (user.UsesKeyConnector) { - Logger.LogWarning("Already uses key connector."); + Logger.LogWarning("Already uses Key Connector."); return IdentityResult.Failed(_identityErrorDescriber.UserAlreadyHasPassword()); } @@ -740,7 +740,7 @@ namespace Bit.Core.Services if (user.UsesKeyConnector) { - throw new BadRequestException("Cannot reset password of a user with key connector."); + throw new BadRequestException("Cannot reset password of a user with Key Connector."); } var result = await UpdatePasswordHash(user, newMasterPassword); @@ -1387,7 +1387,7 @@ namespace Bit.Core.Services if (!user.UsesKeyConnector) { - throw new BadRequestException("Not using key connector."); + throw new BadRequestException("Not using Key Connector."); } var token = await base.GenerateUserTokenAsync(user, TokenOptions.DefaultEmailProvider, diff --git a/src/Core/Utilities/StaticStore.cs b/src/Core/Utilities/StaticStore.cs index f809adc062..c6fb25c6d1 100644 --- a/src/Core/Utilities/StaticStore.cs +++ b/src/Core/Utilities/StaticStore.cs @@ -410,6 +410,7 @@ namespace Bit.Core.Utilities Has2fa = true, HasApi = true, HasSso = true, + HasKeyConnector = true, HasResetPassword = true, UsersGetPremium = true, @@ -448,6 +449,7 @@ namespace Bit.Core.Utilities HasApi = true, HasSelfHost = true, HasSso = true, + HasKeyConnector = true, HasResetPassword = true, UsersGetPremium = true, diff --git a/src/Sql/dbo/Stored Procedures/Organization_Create.sql b/src/Sql/dbo/Stored Procedures/Organization_Create.sql index 22e190b1c8..3f467e4a60 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_Create.sql @@ -40,7 +40,8 @@ @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), @OwnersNotifiedOfAutoscaling DATETIME2(7), - @MaxAutoscaleSeats INT + @MaxAutoscaleSeats INT, + @UseKeyConnector BIT = 0 AS BEGIN SET NOCOUNT ON @@ -88,7 +89,8 @@ BEGIN [CreationDate], [RevisionDate], [OwnersNotifiedOfAutoscaling], - [MaxAutoscaleSeats] + [MaxAutoscaleSeats], + [UseKeyConnector] ) VALUES ( @@ -133,6 +135,7 @@ BEGIN @CreationDate, @RevisionDate, @OwnersNotifiedOfAutoscaling, - @MaxAutoscaleSeats + @MaxAutoscaleSeats, + @UseKeyConnector ) END diff --git a/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql b/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql index 88463a2dc5..cf5219f088 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql @@ -15,6 +15,7 @@ BEGIN END AS [Using2fa], [UsersGetPremium], [UseSso], + [UseKeyConnector], [UseResetPassword], [Enabled] FROM diff --git a/src/Sql/dbo/Stored Procedures/Organization_Update.sql b/src/Sql/dbo/Stored Procedures/Organization_Update.sql index 2d1eb4fb08..a0ff29c7e2 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_Update.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_Update.sql @@ -40,7 +40,8 @@ @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), @OwnersNotifiedOfAutoscaling DATETIME2(7), - @MaxAutoscaleSeats INT + @MaxAutoscaleSeats INT, + @UseKeyConnector BIT = 0 AS BEGIN SET NOCOUNT ON @@ -88,7 +89,8 @@ BEGIN [CreationDate] = @CreationDate, [RevisionDate] = @RevisionDate, [OwnersNotifiedOfAutoscaling] = @OwnersNotifiedOfAutoscaling, - [MaxAutoscaleSeats] = @MaxAutoscaleSeats + [MaxAutoscaleSeats] = @MaxAutoscaleSeats, + [UseKeyConnector] = @UseKeyConnector WHERE [Id] = @Id END diff --git a/src/Sql/dbo/Tables/Organization.sql b/src/Sql/dbo/Tables/Organization.sql index cd1ffcae69..a40a462b2c 100644 --- a/src/Sql/dbo/Tables/Organization.sql +++ b/src/Sql/dbo/Tables/Organization.sql @@ -41,6 +41,7 @@ [RevisionDate] DATETIME2 (7) NOT NULL, [OwnersNotifiedOfAutoscaling] DATETIME2(7) NULL, [MaxAutoscaleSeats] INT NULL, + [UseKeyConnector] BIT NOT NULL, 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 c65c3f3287..288ae8612c 100644 --- a/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql +++ b/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql @@ -7,6 +7,7 @@ SELECT O.[Enabled], O.[UsePolicies], O.[UseSso], + O.[UseKeyConnector], O.[UseGroups], O.[UseDirectory], O.[UseEvents], diff --git a/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql b/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql index 94740127eb..27daa48006 100644 --- a/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql +++ b/src/Sql/dbo/Views/ProviderUserProviderOrganizationDetailsView.sql @@ -7,6 +7,7 @@ SELECT O.[Enabled], O.[UsePolicies], O.[UseSso], + O.[UseKeyConnector], O.[UseGroups], O.[UseDirectory], O.[UseEvents], diff --git a/test/Api.Test/Controllers/OrganizationsControllerTests.cs b/test/Api.Test/Controllers/OrganizationsControllerTests.cs index 9fa3b8ca6d..4011c72b03 100644 --- a/test/Api.Test/Controllers/OrganizationsControllerTests.cs +++ b/test/Api.Test/Controllers/OrganizationsControllerTests.cs @@ -10,6 +10,7 @@ using NSubstitute; using System.Threading.Tasks; using System.Security.Claims; using System; +using Bit.Core.Models.Data; using Xunit; namespace Bit.Api.Test.Controllers @@ -59,7 +60,10 @@ namespace Bit.Api.Test.Controllers var ssoConfig = new SsoConfig { Id = default, - Data = "{\"useKeyConnector\": true}", + Data = new SsoConfigurationData + { + KeyConnectorEnabled = true, + }.Serialize(), Enabled = true, OrganizationId = orgId, }; diff --git a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/OrganizationCompare.cs b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/OrganizationCompare.cs index d8f51febf1..04fa1b10a9 100644 --- a/test/Core.Test/Repositories/EntityFramework/EqualityComparers/OrganizationCompare.cs +++ b/test/Core.Test/Repositories/EntityFramework/EqualityComparers/OrganizationCompare.cs @@ -25,6 +25,7 @@ namespace Bit.Core.Test.Repositories.EntityFramework.EqualityComparers x.MaxCollections.Equals(y.MaxCollections) && x.UsePolicies.Equals(y.UsePolicies) && x.UseSso.Equals(y.UseSso) && + x.UseKeyConnector.Equals(y.UseKeyConnector) && x.UseGroups.Equals(y.UseGroups) && x.UseDirectory.Equals(y.UseDirectory) && x.UseEvents.Equals(y.UseEvents) && diff --git a/test/Core.Test/Services/OrganizationServiceTests.cs b/test/Core.Test/Services/OrganizationServiceTests.cs index 8a505a1cd5..a860da7851 100644 --- a/test/Core.Test/Services/OrganizationServiceTests.cs +++ b/test/Core.Test/Services/OrganizationServiceTests.cs @@ -909,7 +909,7 @@ namespace Bit.Core.Test.Services SsoConfig ssoConfig) { ssoConfig.Enabled = true; - ssoConfig.SetData(new SsoConfigurationData { UseKeyConnector = true }); + ssoConfig.SetData(new SsoConfigurationData { KeyConnectorEnabled = true }); var ssoConfigRepository = sutProvider.GetDependency(); var organizationRepository = sutProvider.GetDependency(); var applicationCacheService = sutProvider.GetDependency(); diff --git a/test/Core.Test/Services/PolicyServiceTests.cs b/test/Core.Test/Services/PolicyServiceTests.cs index dc7705dc1a..00ad9dcaec 100644 --- a/test/Core.Test/Services/PolicyServiceTests.cs +++ b/test/Core.Test/Services/PolicyServiceTests.cs @@ -144,7 +144,7 @@ namespace Bit.Core.Test.Services }); var ssoConfig = new SsoConfig { Enabled = true }; - var data = new SsoConfigurationData { UseKeyConnector = true }; + var data = new SsoConfigurationData { KeyConnectorEnabled = true }; ssoConfig.SetData(data); sutProvider.GetDependency() diff --git a/test/Core.Test/Services/SsoConfigServiceTests.cs b/test/Core.Test/Services/SsoConfigServiceTests.cs index 0205a1e760..ecb732e948 100644 --- a/test/Core.Test/Services/SsoConfigServiceTests.cs +++ b/test/Core.Test/Services/SsoConfigServiceTests.cs @@ -15,9 +15,9 @@ namespace Bit.Core.Test.Services public class SsoConfigServiceTests { [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task SaveAsync_ExistingItem_UpdatesRevisionDateOnly(SutProvider sutProvider) + public async Task SaveAsync_ExistingItem_UpdatesRevisionDateOnly(SutProvider sutProvider, + Organization organization) { - var utcNow = DateTime.UtcNow; var ssoConfig = new SsoConfig @@ -25,7 +25,7 @@ namespace Bit.Core.Test.Services Id = 1, Data = "{}", Enabled = true, - OrganizationId = Guid.NewGuid(), + OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow.AddDays(-10), }; @@ -33,7 +33,7 @@ namespace Bit.Core.Test.Services sutProvider.GetDependency() .UpsertAsync(ssoConfig).Returns(Task.CompletedTask); - await sutProvider.Sut.SaveAsync(ssoConfig); + await sutProvider.Sut.SaveAsync(ssoConfig, organization); await sutProvider.GetDependency().Received() .UpsertAsync(ssoConfig); @@ -43,7 +43,8 @@ namespace Bit.Core.Test.Services } [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task SaveAsync_NewItem_UpdatesCreationAndRevisionDate(SutProvider sutProvider) + public async Task SaveAsync_NewItem_UpdatesCreationAndRevisionDate(SutProvider sutProvider, + Organization organization) { var utcNow = DateTime.UtcNow; @@ -52,7 +53,7 @@ namespace Bit.Core.Test.Services Id = default, Data = "{}", Enabled = true, - OrganizationId = Guid.NewGuid(), + OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow.AddDays(-10), }; @@ -60,7 +61,7 @@ namespace Bit.Core.Test.Services sutProvider.GetDependency() .UpsertAsync(ssoConfig).Returns(Task.CompletedTask); - await sutProvider.Sut.SaveAsync(ssoConfig); + await sutProvider.Sut.SaveAsync(ssoConfig, organization); await sutProvider.GetDependency().Received() .UpsertAsync(ssoConfig); @@ -70,16 +71,20 @@ namespace Bit.Core.Test.Services } [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task SaveAsync_PreventDisablingKeyConnector(SutProvider sutProvider, Guid orgId) + public async Task SaveAsync_PreventDisablingKeyConnector(SutProvider sutProvider, + Organization organization) { var utcNow = DateTime.UtcNow; var oldSsoConfig = new SsoConfig { Id = 1, - Data = "{\"useKeyConnector\": true}", + Data = new SsoConfigurationData + { + KeyConnectorEnabled = true, + }.Serialize(), Enabled = true, - OrganizationId = orgId, + OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow.AddDays(-10), }; @@ -89,19 +94,19 @@ namespace Bit.Core.Test.Services Id = 1, Data = "{}", Enabled = true, - OrganizationId = orgId, + OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow, }; var ssoConfigRepository = sutProvider.GetDependency(); - ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(oldSsoConfig); + ssoConfigRepository.GetByOrganizationIdAsync(organization.Id).Returns(oldSsoConfig); ssoConfigRepository.UpsertAsync(newSsoConfig).Returns(Task.CompletedTask); - sutProvider.GetDependency().GetManyDetailsByOrganizationAsync(orgId) + sutProvider.GetDependency().GetManyDetailsByOrganizationAsync(organization.Id) .Returns(new[] { new OrganizationUserUserDetails { UsesKeyConnector = true } }); var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(newSsoConfig)); + () => sutProvider.Sut.SaveAsync(newSsoConfig, organization)); Assert.Contains("Key Connector cannot be disabled at this moment.", exception.Message); @@ -111,16 +116,19 @@ namespace Bit.Core.Test.Services [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task SaveAsync_AllowDisablingKeyConnectorWhenNoUserIsUsingIt( - SutProvider sutProvider, Guid orgId) + SutProvider sutProvider, Organization organization) { var utcNow = DateTime.UtcNow; var oldSsoConfig = new SsoConfig { Id = 1, - Data = "{\"useKeyConnector\": true}", + Data = new SsoConfigurationData + { + KeyConnectorEnabled = true, + }.Serialize(), Enabled = true, - OrganizationId = orgId, + OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow.AddDays(-10), }; @@ -130,37 +138,41 @@ namespace Bit.Core.Test.Services Id = 1, Data = "{}", Enabled = true, - OrganizationId = orgId, + OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow, }; var ssoConfigRepository = sutProvider.GetDependency(); - ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(oldSsoConfig); + ssoConfigRepository.GetByOrganizationIdAsync(organization.Id).Returns(oldSsoConfig); ssoConfigRepository.UpsertAsync(newSsoConfig).Returns(Task.CompletedTask); - sutProvider.GetDependency().GetManyDetailsByOrganizationAsync(orgId) + sutProvider.GetDependency().GetManyDetailsByOrganizationAsync(organization.Id) .Returns(new[] { new OrganizationUserUserDetails { UsesKeyConnector = false } }); - await sutProvider.Sut.SaveAsync(newSsoConfig); + await sutProvider.Sut.SaveAsync(newSsoConfig, organization); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task SaveAsync_KeyConnector_SingleOrgNotEnabled_Throws(SutProvider sutProvider) + public async Task SaveAsync_KeyConnector_SingleOrgNotEnabled_Throws(SutProvider sutProvider, + Organization organization) { var utcNow = DateTime.UtcNow; var ssoConfig = new SsoConfig { Id = default, - Data = "{\"useKeyConnector\": true}", + Data = new SsoConfigurationData + { + KeyConnectorEnabled = true, + }.Serialize(), Enabled = true, - OrganizationId = Guid.NewGuid(), + OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow.AddDays(-10), }; var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(ssoConfig)); + () => sutProvider.Sut.SaveAsync(ssoConfig, organization)); Assert.Contains("Key Connector requires the Single Organization policy to be enabled.", exception.Message); @@ -169,16 +181,20 @@ namespace Bit.Core.Test.Services } [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task SaveAsync_KeyConnector_SsoPolicyNotEnabled_Throws(SutProvider sutProvider) + public async Task SaveAsync_KeyConnector_SsoPolicyNotEnabled_Throws(SutProvider sutProvider, + Organization organization) { var utcNow = DateTime.UtcNow; var ssoConfig = new SsoConfig { Id = default, - Data = "{\"useKeyConnector\": true}", + Data = new SsoConfigurationData + { + KeyConnectorEnabled = true, + }.Serialize(), Enabled = true, - OrganizationId = Guid.NewGuid(), + OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow.AddDays(-10), }; @@ -190,7 +206,7 @@ namespace Bit.Core.Test.Services }); var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(ssoConfig)); + () => sutProvider.Sut.SaveAsync(ssoConfig, organization)); Assert.Contains("Key Connector requires the Single Sign-On Authentication policy to be enabled.", exception.Message); @@ -199,16 +215,20 @@ namespace Bit.Core.Test.Services } [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task SaveAsync_KeyConnector_SsoConfigNotEnabled_Throws(SutProvider sutProvider) + public async Task SaveAsync_KeyConnector_SsoConfigNotEnabled_Throws(SutProvider sutProvider, + Organization organization) { var utcNow = DateTime.UtcNow; var ssoConfig = new SsoConfig { Id = default, - Data = "{\"useKeyConnector\": true}", + Data = new SsoConfigurationData + { + KeyConnectorEnabled = true, + }.Serialize(), Enabled = false, - OrganizationId = Guid.NewGuid(), + OrganizationId = organization.Id, CreationDate = utcNow.AddDays(-10), RevisionDate = utcNow.AddDays(-10), }; @@ -220,12 +240,79 @@ namespace Bit.Core.Test.Services }); var exception = await Assert.ThrowsAsync( - () => sutProvider.Sut.SaveAsync(ssoConfig)); + () => sutProvider.Sut.SaveAsync(ssoConfig, organization)); Assert.Contains("You must enable SSO to use Key Connector.", exception.Message); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .UpsertAsync(default); } + + [Theory, CustomAutoData(typeof(SutProviderCustomization))] + public async Task SaveAsync_KeyConnector_KeyConnectorAbilityNotEnabled_Throws(SutProvider sutProvider, + Organization organization) + { + var utcNow = DateTime.UtcNow; + + organization.UseKeyConnector = false; + var ssoConfig = new SsoConfig + { + Id = default, + Data = new SsoConfigurationData + { + KeyConnectorEnabled = true, + }.Serialize(), + Enabled = true, + OrganizationId = organization.Id, + CreationDate = utcNow.AddDays(-10), + RevisionDate = utcNow.AddDays(-10), + }; + + sutProvider.GetDependency().GetByOrganizationIdTypeAsync( + Arg.Any(), Arg.Any()).Returns(new Policy + { + Enabled = true, + }); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.SaveAsync(ssoConfig, organization)); + + Assert.Contains("Organization cannot use Key Connector.", exception.Message); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .UpsertAsync(default); + } + + [Theory, CustomAutoData(typeof(SutProviderCustomization))] + public async Task SaveAsync_KeyConnector_Success(SutProvider sutProvider, + Organization organization) + { + var utcNow = DateTime.UtcNow; + + organization.UseKeyConnector = true; + var ssoConfig = new SsoConfig + { + Id = default, + Data = new SsoConfigurationData + { + KeyConnectorEnabled = true, + }.Serialize(), + Enabled = true, + OrganizationId = organization.Id, + CreationDate = utcNow.AddDays(-10), + RevisionDate = utcNow.AddDays(-10), + }; + + sutProvider.GetDependency().GetByOrganizationIdTypeAsync( + Arg.Any(), Arg.Any()).Returns(new Policy + { + Enabled = true, + }); + + await sutProvider.Sut.SaveAsync(ssoConfig, organization); + + await sutProvider.GetDependency().ReceivedWithAnyArgs() + .UpsertAsync(default); + } } } diff --git a/util/Migrator/DbScripts/2021-11-12_00_KeyConnectorFlag.sql b/util/Migrator/DbScripts/2021-11-12_00_KeyConnectorFlag.sql new file mode 100644 index 0000000000..b216601e56 --- /dev/null +++ b/util/Migrator/DbScripts/2021-11-12_00_KeyConnectorFlag.sql @@ -0,0 +1,420 @@ +IF COL_LENGTH('[dbo].[Organization]', 'UseKeyConnector') IS NULL + BEGIN + ALTER TABLE + [dbo].[Organization] + ADD + [UseKeyConnector] BIT NULL + END +GO + +UPDATE + [dbo].[Organization] +SET + [UseKeyConnector] = 0 +WHERE + [UseKeyConnector] IS NULL +GO + +ALTER TABLE + [dbo].[Organization] + ALTER COLUMN + [UseKeyConnector] BIT NOT NULL +GO + +IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'OrganizationView') + BEGIN + DROP VIEW [dbo].[OrganizationView] + END +GO + +CREATE VIEW [dbo].[OrganizationView] +AS +SELECT + * +FROM + [dbo].[Organization] +GO + +IF OBJECT_ID('[dbo].[Organization_Create]') IS NOT NULL + BEGIN + DROP PROCEDURE [dbo].[Organization_Create] + END +GO + +CREATE PROCEDURE [dbo].[Organization_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @Identifier NVARCHAR(50), + @Name NVARCHAR(50), + @BusinessName NVARCHAR(50), + @BusinessAddress1 NVARCHAR(50), + @BusinessAddress2 NVARCHAR(50), + @BusinessAddress3 NVARCHAR(50), + @BusinessCountry VARCHAR(2), + @BusinessTaxNumber NVARCHAR(30), + @BillingEmail NVARCHAR(256), + @Plan NVARCHAR(50), + @PlanType TINYINT, + @Seats INT, + @MaxCollections SMALLINT, + @UsePolicies BIT, + @UseSso BIT, + @UseGroups BIT, + @UseDirectory BIT, + @UseEvents BIT, + @UseTotp BIT, + @Use2fa BIT, + @UseApi BIT, + @UseResetPassword BIT, + @SelfHost BIT, + @UsersGetPremium BIT, + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @Enabled BIT, + @LicenseKey VARCHAR(100), + @ApiKey VARCHAR(30), + @PublicKey VARCHAR(MAX), + @PrivateKey VARCHAR(MAX), + @TwoFactorProviders NVARCHAR(MAX), + @ExpirationDate DATETIME2(7), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @OwnersNotifiedOfAutoscaling DATETIME2(7), + @MaxAutoscaleSeats INT, + @UseKeyConnector BIT = 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], + [ApiKey], + [PublicKey], + [PrivateKey], + [TwoFactorProviders], + [ExpirationDate], + [CreationDate], + [RevisionDate], + [OwnersNotifiedOfAutoscaling], + [MaxAutoscaleSeats], + [UseKeyConnector] + ) + VALUES + ( + @Id, + @Identifier, + @Name, + @BusinessName, + @BusinessAddress1, + @BusinessAddress2, + @BusinessAddress3, + @BusinessCountry, + @BusinessTaxNumber, + @BillingEmail, + @Plan, + @PlanType, + @Seats, + @MaxCollections, + @UsePolicies, + @UseSso, + @UseGroups, + @UseDirectory, + @UseEvents, + @UseTotp, + @Use2fa, + @UseApi, + @UseResetPassword, + @SelfHost, + @UsersGetPremium, + @Storage, + @MaxStorageGb, + @Gateway, + @GatewayCustomerId, + @GatewaySubscriptionId, + @ReferenceData, + @Enabled, + @LicenseKey, + @ApiKey, + @PublicKey, + @PrivateKey, + @TwoFactorProviders, + @ExpirationDate, + @CreationDate, + @RevisionDate, + @OwnersNotifiedOfAutoscaling, + @MaxAutoscaleSeats, + @UseKeyConnector + ) +END +GO + +IF OBJECT_ID('[dbo].[Organization_ReadAbilities]') IS NOT NULL + BEGIN + DROP PROCEDURE [dbo].[Organization_ReadAbilities] + END +GO + +CREATE PROCEDURE [dbo].[Organization_ReadAbilities] +AS +BEGIN + SET NOCOUNT ON + + SELECT + [Id], + [UseEvents], + [Use2fa], + CASE + WHEN [Use2fa] = 1 AND [TwoFactorProviders] IS NOT NULL AND [TwoFactorProviders] != '{}' THEN + 1 + ELSE + 0 + END AS [Using2fa], + [UsersGetPremium], + [UseSso], + [UseKeyConnector], + [UseResetPassword], + [Enabled] + FROM + [dbo].[Organization] +END +GO + +IF OBJECT_ID('[dbo].[Organization_Update]') IS NOT NULL + BEGIN + DROP PROCEDURE [dbo].[Organization_Update] + END +GO + +CREATE 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), + @ApiKey VARCHAR(30), + @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 +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, + [ApiKey] = @ApiKey, + [PublicKey] = @PublicKey, + [PrivateKey] = @PrivateKey, + [TwoFactorProviders] = @TwoFactorProviders, + [ExpirationDate] = @ExpirationDate, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate, + [OwnersNotifiedOfAutoscaling] = @OwnersNotifiedOfAutoscaling, + [MaxAutoscaleSeats] = @MaxAutoscaleSeats, + [UseKeyConnector] = @UseKeyConnector + WHERE + [Id] = @Id +END +GO + +IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'OrganizationUserOrganizationDetailsView') + BEGIN + DROP VIEW [dbo].[OrganizationUserOrganizationDetailsView] + END +GO + +CREATE VIEW [dbo].[OrganizationUserOrganizationDetailsView] +AS +SELECT + OU.[UserId], + OU.[OrganizationId], + O.[Name], + O.[Enabled], + O.[UsePolicies], + O.[UseSso], + O.[UseKeyConnector], + O.[UseGroups], + O.[UseDirectory], + O.[UseEvents], + O.[UseTotp], + O.[Use2fa], + O.[UseApi], + O.[UseResetPassword], + O.[SelfHost], + O.[UsersGetPremium], + 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, + SS.[Data] SsoConfig +FROM + [dbo].[OrganizationUser] OU +INNER 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] +GO + +IF OBJECT_ID('[dbo].[ProviderUserProviderOrganizationDetailsView]') IS NOT NULL + BEGIN + DROP VIEW [dbo].[ProviderUserProviderOrganizationDetailsView] + END +GO + +CREATE VIEW [dbo].[ProviderUserProviderOrganizationDetailsView] +AS +SELECT + PU.[UserId], + PO.[OrganizationId], + O.[Name], + O.[Enabled], + O.[UsePolicies], + O.[UseSso], + O.[UseKeyConnector], + O.[UseGroups], + O.[UseDirectory], + O.[UseEvents], + O.[UseTotp], + O.[Use2fa], + O.[UseApi], + O.[UseResetPassword], + O.[SelfHost], + O.[UsersGetPremium], + O.[Seats], + O.[MaxCollections], + O.[MaxStorageGb], + O.[Identifier], + PO.[Key], + O.[PublicKey], + O.[PrivateKey], + PU.[Status], + PU.[Type], + PO.[ProviderId], + PU.[Id] ProviderUserId, + P.[Name] ProviderName +FROM + [dbo].[ProviderUser] PU +INNER JOIN + [dbo].[ProviderOrganization] PO ON PO.[ProviderId] = PU.[ProviderId] +INNER JOIN + [dbo].[Organization] O ON O.[Id] = PO.[OrganizationId] +INNER JOIN + [dbo].[Provider] P ON P.[Id] = PU.[ProviderId] diff --git a/util/MySqlMigrations/Migrations/20211115145402_KeyConnectorFlag.Designer.cs b/util/MySqlMigrations/Migrations/20211115145402_KeyConnectorFlag.Designer.cs new file mode 100644 index 0000000000..287ac5bdac --- /dev/null +++ b/util/MySqlMigrations/Migrations/20211115145402_KeyConnectorFlag.Designer.cs @@ -0,0 +1,1498 @@ +// +using System; +using Bit.Core.Repositories.EntityFramework; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Bit.MySqlMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20211115145402_KeyConnectorFlag")] + partial class KeyConnectorFlag + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 64) + .HasAnnotation("ProductVersion", "5.0.9"); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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.Core.Models.EntityFramework.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Event"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Policy", b => + { + b.Property("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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.U2f", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AppId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Challenge") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("KeyHandle") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("Version") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("U2f"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("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("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionCipher", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionGroup", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Device", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.EmergencyAccess", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Folder", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.GroupUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Policy", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Send", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoConfig", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Transaction", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.U2f", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("U2fs") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Organization", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + + b.Navigation("U2fs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20211115145402_KeyConnectorFlag.cs b/util/MySqlMigrations/Migrations/20211115145402_KeyConnectorFlag.cs new file mode 100644 index 0000000000..d68eb65a17 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20211115145402_KeyConnectorFlag.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Bit.MySqlMigrations.Migrations +{ + public partial class KeyConnectorFlag : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UseKeyConnector", + table: "Organization", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UseKeyConnector", + table: "Organization"); + } + } +} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index ce4df31bda..50709984d4 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -565,6 +565,9 @@ namespace Bit.MySqlMigrations.Migrations b.Property("UseGroups") .HasColumnType("tinyint(1)"); + b.Property("UseKeyConnector") + .HasColumnType("tinyint(1)"); + b.Property("UsePolicies") .HasColumnType("tinyint(1)"); diff --git a/util/MySqlMigrations/MySqlMigrations.csproj b/util/MySqlMigrations/MySqlMigrations.csproj index d197ece0cf..277d928c3a 100644 --- a/util/MySqlMigrations/MySqlMigrations.csproj +++ b/util/MySqlMigrations/MySqlMigrations.csproj @@ -2,6 +2,7 @@ net5.0 + 9f1cd3e0-70f2-4921-8068-b2538fd7c3f7 diff --git a/util/MySqlMigrations/Scripts/2021-11-12_00_KeyConnectorFlag.sql b/util/MySqlMigrations/Scripts/2021-11-12_00_KeyConnectorFlag.sql new file mode 100644 index 0000000000000000000000000000000000000000..9de11d2a233e11cb982df666a1289e28a59b23a1 GIT binary patch literal 492 zcmZ{hPfG$(6vfXv=sUP`20}D)5v?X0X=t3nQMVc?Lm)HY*up-1^*b*kA>wl1yMNC; z_r1@LQZpr*a(2)ucA=7UgqP_N??&f33z{e=no{N3M7Q`U_KLclDm|;#tKPZy+zsA} zY@$2FRdco4=m9-|?|hB@LUlvGYu#6q^|@E1fnuTs_~uuDkRu)NQjI~j?~Lsn-suV3 zENo)7uCO-)$2^vZ`zOq)nEcDP)jhHH$mkF_MVx;tGp&hV1iU9O8vM7g%YvPDVEnv5 z9Numb7|xkn(i5qxOZCygQHI=C8lsLa|9z#gw(za!_v|hmJvw^2#*R<6AF=BM$=Le) G2fqOGuS*&L literal 0 HcmV?d00001 diff --git a/util/PostgresMigrations/Migrations/20211115142623_KeyConnectorFlag.Designer.cs b/util/PostgresMigrations/Migrations/20211115142623_KeyConnectorFlag.Designer.cs new file mode 100644 index 0000000000..63628cfe1d --- /dev/null +++ b/util/PostgresMigrations/Migrations/20211115142623_KeyConnectorFlag.Designer.cs @@ -0,0 +1,1507 @@ +// +using System; +using Bit.Core.Repositories.EntityFramework; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Bit.PostgresMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20211115142623_KeyConnectorFlag")] + partial class KeyConnectorFlag + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:postgresIndetermanisticCollation", "en-u-ks-primary,en-u-ks-primary,icu,False") + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.9") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp without 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 without time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp without 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 without time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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.Core.Models.EntityFramework.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp without 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 without time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp without 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 without time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp without time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without 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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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 without time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Event"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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 without time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without 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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp without 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 without time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp without 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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + 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 without time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without 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 without 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 without time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp without 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 without 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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp without 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 without 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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without 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 without time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CreationDate") + .HasColumnType("timestamp without 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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp without 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"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.U2f", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AppId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Challenge") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("KeyHandle") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("Version") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("U2f"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp without 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("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("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 without time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp without time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionCipher", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionGroup", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Device", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.EmergencyAccess", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Folder", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.GroupUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Policy", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Send", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoConfig", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Transaction", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.U2f", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("U2fs") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Organization", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + + b.Navigation("U2fs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20211115142623_KeyConnectorFlag.cs b/util/PostgresMigrations/Migrations/20211115142623_KeyConnectorFlag.cs new file mode 100644 index 0000000000..edc5220861 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20211115142623_KeyConnectorFlag.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Bit.PostgresMigrations.Migrations +{ + public partial class KeyConnectorFlag : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UseKeyConnector", + table: "Organization", + type: "boolean", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UseKeyConnector", + table: "Organization"); + } + } +} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index 51d27d5395..1c4b70b115 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -569,6 +569,9 @@ namespace Bit.PostgresMigrations.Migrations b.Property("UseGroups") .HasColumnType("boolean"); + b.Property("UseKeyConnector") + .HasColumnType("boolean"); + b.Property("UsePolicies") .HasColumnType("boolean"); diff --git a/util/PostgresMigrations/Scripts/2021-11-12_00_KeyConnectorFlag.psql b/util/PostgresMigrations/Scripts/2021-11-12_00_KeyConnectorFlag.psql new file mode 100644 index 0000000000000000000000000000000000000000..a65e3498691c9732b7fd3ac07219d1ab48d90783 GIT binary patch literal 486 zcmZ{hTWi8V5QWdP;C~3d#6S_P7Z9IPjWje?F~(O(wMd~DF!sUz_||W>76p-Ib}oDN z%$eQapHvHlnzMV@ocL8K`;aWwlx(1?D$YVBR3lJ^4fRAG5!b-&wNm6h6N?iRM^`#y zH-qUkgk=kOOu>p}_b2Gv((|IU)HAj6tmWv`)Y)&5u{P8n9N!F%g8a)(u|)SCj{n&M z_i9V0Fr#nEow|HIs)@DEtBdcD_pmnl?!C{Lcga!hyWYOKYQWP_pGt5w{g7EFteC0) G{_X_kvq%2` literal 0 HcmV?d00001 From faa8abb3dc45cf4cc70810a28dcdf568343d876e Mon Sep 17 00:00:00 2001 From: Micaiah Martin <77340197+mimartin12@users.noreply.github.com> Date: Wed, 17 Nov 2021 07:54:38 -0700 Subject: [PATCH 08/10] Updated SETUP.md to direct user with correct file name. (#1719) --- SETUP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SETUP.md b/SETUP.md index d001290a55..3f0cd199bb 100644 --- a/SETUP.md +++ b/SETUP.md @@ -112,7 +112,7 @@ For more information, see: [Safe storage of app secrets in development in ASP.NE We provide a helper scripts which simplifies setting user secrets for all projects in the repository. -Start by copying the `secret.json.example` file to `secret.json` and modify the existing settings and add any other required setting. Afterwards run the following command which will add the settings to each project in the bitwarden repository. +Start by copying the `secret.json.example` file to `secrets.json` and modify the existing settings and add any other required setting. Afterwards run the following command which will add the settings to each project in the bitwarden repository. ```powershell .\setup_secrets.ps1 From 9f96e4ce90a24f9830bf1ab117ce3d3167d460a7 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Thu, 18 Nov 2021 07:47:43 +1000 Subject: [PATCH 09/10] Disable EA Takeover if grantor uses Key Connector (#1718) --- .../Implementations/EmergencyAccessService.cs | 39 +++++- .../Services/EmergencyAccessServiceTests.cs | 117 ++++++++++++++++++ 2 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 test/Core.Test/Services/EmergencyAccessServiceTests.cs diff --git a/src/Core/Services/Implementations/EmergencyAccessService.cs b/src/Core/Services/Implementations/EmergencyAccessService.cs index ca88348afe..a647c07717 100644 --- a/src/Core/Services/Implementations/EmergencyAccessService.cs +++ b/src/Core/Services/Implementations/EmergencyAccessService.cs @@ -62,10 +62,15 @@ namespace Bit.Core.Services public async Task InviteAsync(User invitingUser, string email, EmergencyAccessType type, int waitTime) { - if (! await _userService.CanAccessPremium(invitingUser)) + if (!await _userService.CanAccessPremium(invitingUser)) { throw new BadRequestException("Not a premium user."); } + + if (type == EmergencyAccessType.Takeover && invitingUser.UsesKeyConnector) + { + throw new BadRequestException("You cannot use Emergency Access Takeover because you are using Key Connector."); + } var emergencyAccess = new EmergencyAccess { @@ -171,6 +176,11 @@ namespace Bit.Core.Services } var grantor = await _userRepository.GetByIdAsync(confirmingUserId); + if (emergencyAccess.Type == EmergencyAccessType.Takeover && grantor.UsesKeyConnector) + { + throw new BadRequestException("You cannot use Emergency Access Takeover because you are using Key Connector."); + } + var grantee = await _userRepository.GetByIdAsync(emergencyAccess.GranteeId.Value); emergencyAccess.Status = EmergencyAccessStatusType.Confirmed; @@ -188,7 +198,16 @@ namespace Bit.Core.Services { throw new BadRequestException("Emergency Access not valid."); } - + + if (emergencyAccess.Type == EmergencyAccessType.Takeover) + { + var grantor = await _userService.GetUserByIdAsync(emergencyAccess.GrantorId); + if (grantor.UsesKeyConnector) + { + throw new BadRequestException("You cannot use Emergency Access Takeover because you are using Key Connector."); + } + } + await _emergencyAccessRepository.ReplaceAsync(emergencyAccess); } @@ -202,6 +221,13 @@ namespace Bit.Core.Services throw new BadRequestException("Emergency Access not valid."); } + var grantor = await _userRepository.GetByIdAsync(emergencyAccess.GrantorId); + + if (emergencyAccess.Type == EmergencyAccessType.Takeover && grantor.UsesKeyConnector) + { + throw new BadRequestException("You cannot takeover an account that is using Key Connector."); + } + var now = DateTime.UtcNow; emergencyAccess.Status = EmergencyAccessStatusType.RecoveryInitiated; emergencyAccess.RevisionDate = now; @@ -209,8 +235,6 @@ namespace Bit.Core.Services emergencyAccess.LastNotificationDate = now; await _emergencyAccessRepository.ReplaceAsync(emergencyAccess); - var grantor = await _userRepository.GetByIdAsync(emergencyAccess.GrantorId); - await _mailService.SendEmergencyAccessRecoveryInitiated(emergencyAccess, NameOrEmail(initiatingUser), grantor.Email); } @@ -277,7 +301,12 @@ namespace Bit.Core.Services } var grantor = await _userRepository.GetByIdAsync(emergencyAccess.GrantorId); - + + if (emergencyAccess.Type == EmergencyAccessType.Takeover && grantor.UsesKeyConnector) + { + throw new BadRequestException("You cannot takeover an account that is using Key Connector."); + } + return (emergencyAccess, grantor); } diff --git a/test/Core.Test/Services/EmergencyAccessServiceTests.cs b/test/Core.Test/Services/EmergencyAccessServiceTests.cs new file mode 100644 index 0000000000..f3812f20f1 --- /dev/null +++ b/test/Core.Test/Services/EmergencyAccessServiceTests.cs @@ -0,0 +1,117 @@ +using Bit.Core.Exceptions; +using Bit.Core.Models.Table; +using Bit.Core.Repositories; +using Bit.Core.Services; +using Bit.Core.Test.AutoFixture.Attributes; +using Bit.Core.Test.AutoFixture; +using NSubstitute; +using System.Threading.Tasks; +using System; +using Xunit; + +namespace Bit.Core.Test.Services +{ + public class EmergencyAccessServiceTests + { + [Theory, CustomAutoData(typeof(SutProviderCustomization))] + public async Task InviteAsync_UserWithKeyConnectorCannotUseTakeover( + SutProvider sutProvider, User invitingUser, string email, int waitTime) + { + invitingUser.UsesKeyConnector = true; + sutProvider.GetDependency().CanAccessPremium(invitingUser).Returns(true); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.InviteAsync(invitingUser, email, Enums.EmergencyAccessType.Takeover, waitTime)); + + Assert.Contains("You cannot use Emergency Access Takeover because you are using Key Connector", exception.Message); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().CreateAsync(default); + } + + [Theory, CustomAutoData(typeof(SutProviderCustomization))] + public async Task ConfirmUserAsync_UserWithKeyConnectorCannotUseTakeover( + SutProvider sutProvider, User confirmingUser, string key) + { + confirmingUser.UsesKeyConnector = true; + var emergencyAccess = new EmergencyAccess + { + Status = Enums.EmergencyAccessStatusType.Accepted, + GrantorId = confirmingUser.Id, + Type = Enums.EmergencyAccessType.Takeover, + }; + + sutProvider.GetDependency().GetByIdAsync(confirmingUser.Id).Returns(confirmingUser); + sutProvider.GetDependency().GetByIdAsync(Arg.Any()).Returns(emergencyAccess); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.ConfirmUserAsync(new Guid(), key, confirmingUser.Id)); + + Assert.Contains("You cannot use Emergency Access Takeover because you are using Key Connector", exception.Message); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); + } + + [Theory, CustomAutoData(typeof(SutProviderCustomization))] + public async Task SaveAsync_UserWithKeyConnectorCannotUseTakeover( + SutProvider sutProvider, User savingUser) + { + savingUser.UsesKeyConnector = true; + var emergencyAccess = new EmergencyAccess + { + Type = Enums.EmergencyAccessType.Takeover, + GrantorId = savingUser.Id, + }; + + sutProvider.GetDependency().GetUserByIdAsync(savingUser.Id).Returns(savingUser); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.SaveAsync(emergencyAccess, savingUser.Id)); + + Assert.Contains("You cannot use Emergency Access Takeover because you are using Key Connector", exception.Message); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); + } + + [Theory, CustomAutoData(typeof(SutProviderCustomization))] + public async Task InitiateAsync_UserWithKeyConnectorCannotUseTakeover( + SutProvider sutProvider, User initiatingUser, User grantor) + { + grantor.UsesKeyConnector = true; + var emergencyAccess = new EmergencyAccess + { + Status = Enums.EmergencyAccessStatusType.Confirmed, + GranteeId = initiatingUser.Id, + GrantorId = grantor.Id, + Type = Enums.EmergencyAccessType.Takeover, + }; + + sutProvider.GetDependency().GetByIdAsync(Arg.Any()).Returns(emergencyAccess); + sutProvider.GetDependency().GetByIdAsync(grantor.Id).Returns(grantor); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.InitiateAsync(new Guid(), initiatingUser)); + + Assert.Contains("You cannot takeover an account that is using Key Connector", exception.Message); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().ReplaceAsync(default); + } + + [Theory, CustomAutoData(typeof(SutProviderCustomization))] + public async Task TakeoverAsync_UserWithKeyConnectorCannotUseTakeover( + SutProvider sutProvider, User requestingUser, User grantor) + { + grantor.UsesKeyConnector = true; + var emergencyAccess = new EmergencyAccess + { + GrantorId = grantor.Id, + GranteeId = requestingUser.Id, + Status = Enums.EmergencyAccessStatusType.RecoveryApproved, + Type = Enums.EmergencyAccessType.Takeover, + }; + + sutProvider.GetDependency().GetByIdAsync(Arg.Any()).Returns(emergencyAccess); + sutProvider.GetDependency().GetByIdAsync(grantor.Id).Returns(grantor); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.TakeoverAsync(new Guid(), requestingUser)); + + Assert.Contains("You cannot takeover an account that is using Key Connector", exception.Message); + } + } +} From 2dc29e51d1346e429dad5548692fba45dda70832 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Thu, 18 Nov 2021 21:15:22 +1000 Subject: [PATCH 10/10] Fix bug preventing user from leaving org (#1721) --- .../Controllers/OrganizationsController.cs | 5 ++- .../OrganizationsControllerTests.cs | 40 +++++++++++++++++-- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/Api/Controllers/OrganizationsController.cs b/src/Api/Controllers/OrganizationsController.cs index eb977568fc..e85f05343e 100644 --- a/src/Api/Controllers/OrganizationsController.cs +++ b/src/Api/Controllers/OrganizationsController.cs @@ -385,9 +385,10 @@ namespace Bit.Api.Controllers } var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(orgGuidId); - if (ssoConfig?.GetData()?.KeyConnectorEnabled == true) + if (ssoConfig?.GetData()?.KeyConnectorEnabled == true && + _currentContext.User.UsesKeyConnector) { - throw new BadRequestException("You cannot leave an Organization that is using Key Connector."); + throw new BadRequestException("You cannot leave this Organization because you are using its Key Connector."); } var userId = _userService.GetProperUserId(User); diff --git a/test/Api.Test/Controllers/OrganizationsControllerTests.cs b/test/Api.Test/Controllers/OrganizationsControllerTests.cs index 4011c72b03..6b69a7e971 100644 --- a/test/Api.Test/Controllers/OrganizationsControllerTests.cs +++ b/test/Api.Test/Controllers/OrganizationsControllerTests.cs @@ -54,8 +54,8 @@ namespace Bit.Api.Test.Controllers } [Theory, AutoData] - public async Task OrganizationsController_WhenUserTriestoLeaveOrganizationUsingKeyConnector_Throws( - Guid orgId) + public async Task OrganizationsController_UserCannotLeaveOrganizationThatProvidesKeyConnector( + Guid orgId, User user) { var ssoConfig = new SsoConfig { @@ -68,18 +68,50 @@ namespace Bit.Api.Test.Controllers OrganizationId = orgId, }; + user.UsesKeyConnector = true; + _currentContext.OrganizationUser(orgId).Returns(true); _ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig); - _userService.GetProperUserId(Arg.Any()).Returns(new Guid()); + _userService.GetProperUserId(Arg.Any()).Returns(user.Id); + _currentContext.User.Returns(user); var exception = await Assert.ThrowsAsync( () => _sut.Leave(orgId.ToString())); - Assert.Contains("You cannot leave an Organization that is using Key Connector.", + Assert.Contains("You cannot leave this Organization because you are using its Key Connector.", exception.Message); await _organizationService.DidNotReceiveWithAnyArgs().DeleteUserAsync(default, default); } + + [Theory] + [InlineAutoData(true, false)] + [InlineAutoData(false, true)] + [InlineAutoData(false, false)] + public async Task OrganizationsController_UserCanLeaveOrganizationThatDoesntProvideKeyConnector( + bool keyConnectorEnabled, bool userUsesKeyConnector, Guid orgId, User user) + { + var ssoConfig = new SsoConfig + { + Id = default, + Data = new SsoConfigurationData + { + KeyConnectorEnabled = keyConnectorEnabled, + }.Serialize(), + Enabled = true, + OrganizationId = orgId, + }; + + user.UsesKeyConnector = userUsesKeyConnector; + + _currentContext.OrganizationUser(orgId).Returns(true); + _ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig); + _userService.GetProperUserId(Arg.Any()).Returns(user.Id); + _currentContext.User.Returns(user); + + await _organizationService.DeleteUserAsync(orgId, user.Id); + await _organizationService.Received(1).DeleteUserAsync(orgId, user.Id); + } } }