diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7d87fce8f8..e129ffb1e8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,6 +9,11 @@ # DevOps for Actions and other workflow changes. .github/workflows @bitwarden/dept-devops +# DevOps for Docker changes. +**/Dockerfile @bitwarden/dept-devops +**/*.Dockerfile @bitwarden/dept-devops +**/.dockerignore @bitwarden/dept-devops + ## Auth team files ## **/Auth @bitwarden/team-auth-dev bitwarden_license/src/Sso @bitwarden/team-auth-dev @@ -16,13 +21,16 @@ src/Identity @bitwarden/team-auth-dev **/SecretsManager @bitwarden/team-secrets-manager-dev **/Tools @bitwarden/team-tools-dev + +## Vault Team files **/Vault @bitwarden/team-vault-dev +**/Vault/AuthorizationHandlers @bitwarden/team-vault-dev @bitwarden/team-admin-console-dev # joint ownership over authorization handlers that affect organization users # Admin-Console Team +**/AdminConsole @bitwarden/team-admin-console-dev bitwarden_license/src/Scim @bitwarden/team-admin-console-dev bitwarden_license/src/test/Scim.IntegrationTest @bitwarden/team-admin-console-dev bitwarden_license/src/test/Scim.ScimTest @bitwarden/team-admin-console-dev -**/AdminConsole @bitwarden/team-admin-console-dev # Billing Team **/*billing* @bitwarden/team-billing-dev diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c394585aa9..8fc28e2921 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -277,7 +277,7 @@ jobs: - name: Retrieve github PAT secrets id: retrieve-secret-pat - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "github-pat-bitwarden-devops-bot-repo-scope" @@ -528,7 +528,7 @@ jobs: - name: Retrieve github PAT secrets id: retrieve-secret-pat - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "github-pat-bitwarden-devops-bot-repo-scope" @@ -603,7 +603,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf if: failure() with: keyvault: "bitwarden-ci" diff --git a/.github/workflows/container-registry-purge.yml b/.github/workflows/container-registry-purge.yml index 3fef44b35a..0a42d465d6 100644 --- a/.github/workflows/container-registry-purge.yml +++ b/.github/workflows/container-registry-purge.yml @@ -92,7 +92,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf if: failure() with: keyvault: "bitwarden-ci" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 73c6a779f2..b22eef8ff5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,7 +41,7 @@ jobs: - name: Check Release Version id: version - uses: bitwarden/gh-actions/release-version-check@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/release-version-check@c970b0fb89bd966749280e832928db62040812bf with: release-type: ${{ github.event.inputs.release_type }} project-type: dotnet @@ -89,7 +89,7 @@ jobs: - name: Download latest Release ${{ matrix.name }} asset if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build.yml workflow_conclusion: success @@ -98,7 +98,7 @@ jobs: - name: Dry Run - Download latest Release ${{ matrix.name }} asset if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build.yml workflow_conclusion: success @@ -274,7 +274,7 @@ jobs: steps: - name: Download latest Release Docker Stubs if: ${{ github.event.inputs.release_type != 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build.yml workflow_conclusion: success @@ -287,7 +287,7 @@ jobs: - name: Dry Run - Download latest Release Docker Stubs if: ${{ github.event.inputs.release_type == 'Dry Run' }} - uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf with: workflow: build.yml workflow_conclusion: success diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index d37ea906f4..2118056772 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -23,7 +23,7 @@ jobs: - name: Retrieve secrets id: retrieve-secrets - uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf with: keyvault: "bitwarden-ci" secrets: "github-gpg-private-key, github-gpg-private-key-passphrase" @@ -40,7 +40,7 @@ jobs: run: git switch -c version_bump_${{ github.event.inputs.version_number }} - name: Bump Version - Props - uses: bitwarden/gh-actions/version-bump@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/version-bump@c970b0fb89bd966749280e832928db62040812bf with: version: ${{ github.event.inputs.version_number }} file_path: "Directory.Build.props" diff --git a/.github/workflows/workflow-linter.yml b/.github/workflows/workflow-linter.yml index 49388c11f8..5afd077e8d 100644 --- a/.github/workflows/workflow-linter.yml +++ b/.github/workflows/workflow-linter.yml @@ -8,4 +8,4 @@ on: jobs: call-workflow: - uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 + uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@c970b0fb89bd966749280e832928db62040812bf diff --git a/.gitignore b/.gitignore index 4b798f3b74..dd26998cce 100644 --- a/.gitignore +++ b/.gitignore @@ -225,4 +225,4 @@ src/Identity/Identity.zip src/Notifications/Notifications.zip bitwarden_license/src/Portal/Portal.zip bitwarden_license/src/Sso/Sso.zip -src/Api/flags.json +**/src/*/flags.json diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/AccessPolicies/AccessPolicyAuthorizationHandler.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/AccessPolicies/AccessPolicyAuthorizationHandler.cs index 2915b43930..4cb90223e3 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/AccessPolicies/AccessPolicyAuthorizationHandler.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/AccessPolicies/AccessPolicyAuthorizationHandler.cs @@ -1,4 +1,5 @@ -using Bit.Core.Context; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Repositories; using Bit.Core.SecretsManager.AuthorizationRequirements; diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/ServiceAccounts/ServiceAccountAuthorizationHandler.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/ServiceAccounts/ServiceAccountAuthorizationHandler.cs index 2c4e78d109..e1a4405f1d 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/ServiceAccounts/ServiceAccountAuthorizationHandler.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/AuthorizationHandlers/ServiceAccounts/ServiceAccountAuthorizationHandler.cs @@ -56,6 +56,9 @@ public class case not null when requirement == ServiceAccountOperations.RevokeAccessTokens: await CanRevokeAccessTokensAsync(context, requirement, resource); break; + case not null when requirement == ServiceAccountOperations.ReadEvents: + await CanReadEventsAsync(context, requirement, resource); + break; default: throw new ArgumentException("Unsupported operation requirement type provided.", nameof(requirement)); @@ -169,4 +172,19 @@ public class context.Succeed(requirement); } } + + private async Task CanReadEventsAsync(AuthorizationHandlerContext context, + ServiceAccountOperationRequirement requirement, ServiceAccount resource) + { + var (accessClient, userId) = + await _accessClientQuery.GetAccessClientAsync(context.User, resource.OrganizationId); + var access = + await _serviceAccountRepository.AccessToServiceAccountAsync(resource.Id, userId, + accessClient); + + if (access.Read) + { + context.Succeed(requirement); + } + } } diff --git a/bitwarden_license/src/Scim/Controllers/v2/GroupsController.cs b/bitwarden_license/src/Scim/Controllers/v2/GroupsController.cs index 68bdf8f299..5df0b29216 100644 --- a/bitwarden_license/src/Scim/Controllers/v2/GroupsController.cs +++ b/bitwarden_license/src/Scim/Controllers/v2/GroupsController.cs @@ -1,6 +1,7 @@ -using Bit.Core.Enums; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.Repositories; using Bit.Scim.Groups.Interfaces; using Bit.Scim.Models; diff --git a/bitwarden_license/src/Scim/Groups/GetGroupsListQuery.cs b/bitwarden_license/src/Scim/Groups/GetGroupsListQuery.cs index 1afab3a0fc..7055736a4c 100644 --- a/bitwarden_license/src/Scim/Groups/GetGroupsListQuery.cs +++ b/bitwarden_license/src/Scim/Groups/GetGroupsListQuery.cs @@ -1,5 +1,5 @@ -using Bit.Core.Entities; -using Bit.Core.Repositories; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Repositories; using Bit.Scim.Groups.Interfaces; namespace Bit.Scim.Groups; diff --git a/bitwarden_license/src/Scim/Groups/Interfaces/IGetGroupsListQuery.cs b/bitwarden_license/src/Scim/Groups/Interfaces/IGetGroupsListQuery.cs index d2cf5ef0a2..07ff044701 100644 --- a/bitwarden_license/src/Scim/Groups/Interfaces/IGetGroupsListQuery.cs +++ b/bitwarden_license/src/Scim/Groups/Interfaces/IGetGroupsListQuery.cs @@ -1,4 +1,4 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; namespace Bit.Scim.Groups.Interfaces; diff --git a/bitwarden_license/src/Scim/Groups/Interfaces/IPostGroupCommand.cs b/bitwarden_license/src/Scim/Groups/Interfaces/IPostGroupCommand.cs index 76e80f08dd..011199f033 100644 --- a/bitwarden_license/src/Scim/Groups/Interfaces/IPostGroupCommand.cs +++ b/bitwarden_license/src/Scim/Groups/Interfaces/IPostGroupCommand.cs @@ -1,4 +1,5 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Entities; using Bit.Scim.Models; namespace Bit.Scim.Groups.Interfaces; diff --git a/bitwarden_license/src/Scim/Groups/Interfaces/IPutGroupCommand.cs b/bitwarden_license/src/Scim/Groups/Interfaces/IPutGroupCommand.cs index 052a4e554b..8c0b9108c0 100644 --- a/bitwarden_license/src/Scim/Groups/Interfaces/IPutGroupCommand.cs +++ b/bitwarden_license/src/Scim/Groups/Interfaces/IPutGroupCommand.cs @@ -1,4 +1,5 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Entities; using Bit.Scim.Models; namespace Bit.Scim.Groups.Interfaces; diff --git a/bitwarden_license/src/Scim/Groups/PatchGroupCommand.cs b/bitwarden_license/src/Scim/Groups/PatchGroupCommand.cs index 750dcec46c..f31225754f 100644 --- a/bitwarden_license/src/Scim/Groups/PatchGroupCommand.cs +++ b/bitwarden_license/src/Scim/Groups/PatchGroupCommand.cs @@ -1,10 +1,10 @@ using System.Text.Json; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.AdminConsole.Services; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.OrganizationFeatures.Groups.Interfaces; -using Bit.Core.Repositories; -using Bit.Core.Services; using Bit.Scim.Groups.Interfaces; using Bit.Scim.Models; diff --git a/bitwarden_license/src/Scim/Groups/PostGroupCommand.cs b/bitwarden_license/src/Scim/Groups/PostGroupCommand.cs index 00da7a8d7c..80ec5c5e3e 100644 --- a/bitwarden_license/src/Scim/Groups/PostGroupCommand.cs +++ b/bitwarden_license/src/Scim/Groups/PostGroupCommand.cs @@ -1,8 +1,10 @@ -using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.Repositories; using Bit.Scim.Context; using Bit.Scim.Groups.Interfaces; diff --git a/bitwarden_license/src/Scim/Groups/PutGroupCommand.cs b/bitwarden_license/src/Scim/Groups/PutGroupCommand.cs index 0310139ecb..96c67be918 100644 --- a/bitwarden_license/src/Scim/Groups/PutGroupCommand.cs +++ b/bitwarden_license/src/Scim/Groups/PutGroupCommand.cs @@ -1,9 +1,10 @@ -using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.OrganizationFeatures.Groups.Interfaces; -using Bit.Core.Repositories; using Bit.Scim.Context; using Bit.Scim.Groups.Interfaces; using Bit.Scim.Models; diff --git a/bitwarden_license/src/Scim/Models/ScimGroupRequestModel.cs b/bitwarden_license/src/Scim/Models/ScimGroupRequestModel.cs index ac99eca2e9..11bd40c587 100644 --- a/bitwarden_license/src/Scim/Models/ScimGroupRequestModel.cs +++ b/bitwarden_license/src/Scim/Models/ScimGroupRequestModel.cs @@ -1,4 +1,4 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.Utilities; namespace Bit.Scim.Models; diff --git a/bitwarden_license/src/Scim/Models/ScimGroupResponseModel.cs b/bitwarden_license/src/Scim/Models/ScimGroupResponseModel.cs index 7ba209df9b..697a3d59da 100644 --- a/bitwarden_license/src/Scim/Models/ScimGroupResponseModel.cs +++ b/bitwarden_license/src/Scim/Models/ScimGroupResponseModel.cs @@ -1,4 +1,4 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; namespace Bit.Scim.Models; diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/AccessPolicies/AccessPolicyAuthorizationHandlerTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/AccessPolicies/AccessPolicyAuthorizationHandlerTests.cs index 6f95f3c451..b6779394c1 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/AccessPolicies/AccessPolicyAuthorizationHandlerTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/AccessPolicies/AccessPolicyAuthorizationHandlerTests.cs @@ -2,6 +2,8 @@ using System.Security.Claims; using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.AccessPolicies; using Bit.Commercial.Core.Test.SecretsManager.Enums; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/ServiceAccounts/ServiceAccountAuthorizationHandlerTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/ServiceAccounts/ServiceAccountAuthorizationHandlerTests.cs index b7804a700f..eec69095d5 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/ServiceAccounts/ServiceAccountAuthorizationHandlerTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/AuthorizationHandlers/ServiceAccounts/ServiceAccountAuthorizationHandlerTests.cs @@ -497,4 +497,63 @@ public class ServiceAccountAuthorizationHandlerTests Assert.Equal(expected, authzContext.HasSucceeded); } + + [Theory] + [BitAutoData] + public async Task CanReadEvents_AccessToSecretsManagerFalse_DoesNotSucceed( + SutProvider sutProvider, ServiceAccount serviceAccount, + ClaimsPrincipal claimsPrincipal) + { + var requirement = ServiceAccountOperations.ReadEvents; + sutProvider.GetDependency().AccessSecretsManager(serviceAccount.OrganizationId) + .Returns(false); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, serviceAccount); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData] + public async Task CanReadEvents_NullResource_DoesNotSucceed( + SutProvider sutProvider, ServiceAccount serviceAccount, + ClaimsPrincipal claimsPrincipal, + Guid userId) + { + var requirement = ServiceAccountOperations.ReadEvents; + SetupPermission(sutProvider, PermissionType.RunAsAdmin, serviceAccount.OrganizationId, userId); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, null); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.False(authzContext.HasSucceeded); + } + + [Theory] + [BitAutoData(PermissionType.RunAsAdmin, true, true, true)] + [BitAutoData(PermissionType.RunAsUserWithPermission, false, false, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, false, true, false)] + [BitAutoData(PermissionType.RunAsUserWithPermission, true, false, true)] + [BitAutoData(PermissionType.RunAsUserWithPermission, true, true, true)] + public async Task CanReadEvents_AccessCheck(PermissionType permissionType, bool read, bool write, + bool expected, + SutProvider sutProvider, ServiceAccount serviceAccount, + ClaimsPrincipal claimsPrincipal, + Guid userId) + { + var requirement = ServiceAccountOperations.ReadEvents; + SetupPermission(sutProvider, permissionType, serviceAccount.OrganizationId, userId); + sutProvider.GetDependency() + .AccessToServiceAccountAsync(serviceAccount.Id, userId, Arg.Any()) + .Returns((read, write)); + var authzContext = new AuthorizationHandlerContext(new List { requirement }, + claimsPrincipal, serviceAccount); + + await sutProvider.Sut.HandleAsync(authzContext); + + Assert.Equal(expected, authzContext.HasSucceeded); + } } diff --git a/bitwarden_license/test/Scim.Test/Groups/GetGroupsListQueryTests.cs b/bitwarden_license/test/Scim.Test/Groups/GetGroupsListQueryTests.cs index fa8997580c..1599b6e390 100644 --- a/bitwarden_license/test/Scim.Test/Groups/GetGroupsListQueryTests.cs +++ b/bitwarden_license/test/Scim.Test/Groups/GetGroupsListQueryTests.cs @@ -1,5 +1,5 @@ -using Bit.Core.Entities; -using Bit.Core.Repositories; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Repositories; using Bit.Scim.Groups; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; diff --git a/bitwarden_license/test/Scim.Test/Groups/PatchGroupCommandTests.cs b/bitwarden_license/test/Scim.Test/Groups/PatchGroupCommandTests.cs index 505a67f570..066887fe7a 100644 --- a/bitwarden_license/test/Scim.Test/Groups/PatchGroupCommandTests.cs +++ b/bitwarden_license/test/Scim.Test/Groups/PatchGroupCommandTests.cs @@ -1,10 +1,11 @@ using System.Text.Json; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.AdminConsole.Services; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.OrganizationFeatures.Groups.Interfaces; -using Bit.Core.Repositories; -using Bit.Core.Services; using Bit.Scim.Groups; using Bit.Scim.Models; using Bit.Scim.Utilities; diff --git a/bitwarden_license/test/Scim.Test/Groups/PostGroupCommandTests.cs b/bitwarden_license/test/Scim.Test/Groups/PostGroupCommandTests.cs index 40281e4d1b..2d06f23a29 100644 --- a/bitwarden_license/test/Scim.Test/Groups/PostGroupCommandTests.cs +++ b/bitwarden_license/test/Scim.Test/Groups/PostGroupCommandTests.cs @@ -1,9 +1,10 @@ -using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.OrganizationFeatures.Groups.Interfaces; -using Bit.Core.Repositories; using Bit.Scim.Context; using Bit.Scim.Groups; using Bit.Scim.Models; diff --git a/bitwarden_license/test/Scim.Test/Groups/PutGroupCommandTests.cs b/bitwarden_license/test/Scim.Test/Groups/PutGroupCommandTests.cs index ecf77d563b..05f6672779 100644 --- a/bitwarden_license/test/Scim.Test/Groups/PutGroupCommandTests.cs +++ b/bitwarden_license/test/Scim.Test/Groups/PutGroupCommandTests.cs @@ -1,9 +1,10 @@ -using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.OrganizationFeatures.Groups.Interfaces; -using Bit.Core.Repositories; using Bit.Scim.Context; using Bit.Scim.Groups; using Bit.Scim.Models; diff --git a/src/Admin/Controllers/OrganizationsController.cs b/src/Admin/Controllers/OrganizationsController.cs index 7c296dd3c6..b3a232c2c8 100644 --- a/src/Admin/Controllers/OrganizationsController.cs +++ b/src/Admin/Controllers/OrganizationsController.cs @@ -2,6 +2,8 @@ using Bit.Admin.Models; using Bit.Admin.Services; using Bit.Admin.Utilities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; diff --git a/src/Admin/Models/OrganizationEditModel.cs b/src/Admin/Models/OrganizationEditModel.cs index 4e90f5bdeb..f8bfa453da 100644 --- a/src/Admin/Models/OrganizationEditModel.cs +++ b/src/Admin/Models/OrganizationEditModel.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Bit.Core.AdminConsole.Entities; using Bit.Core.Entities; using Bit.Core.Entities.Provider; using Bit.Core.Enums; diff --git a/src/Admin/Models/OrganizationViewModel.cs b/src/Admin/Models/OrganizationViewModel.cs index 7320b8594d..541f7a2fc1 100644 --- a/src/Admin/Models/OrganizationViewModel.cs +++ b/src/Admin/Models/OrganizationViewModel.cs @@ -1,4 +1,5 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Entities; using Bit.Core.Entities.Provider; using Bit.Core.Enums; using Bit.Core.Models.Data.Organizations.OrganizationUsers; diff --git a/src/Api/Controllers/GroupsController.cs b/src/Api/AdminConsole/Controllers/GroupsController.cs similarity index 96% rename from src/Api/Controllers/GroupsController.cs rename to src/Api/AdminConsole/Controllers/GroupsController.cs index 2c75bd3741..45dc47ac10 100644 --- a/src/Api/Controllers/GroupsController.cs +++ b/src/Api/AdminConsole/Controllers/GroupsController.cs @@ -1,16 +1,19 @@ -using Bit.Api.Models.Request; +using Bit.Api.AdminConsole.Models.Request; +using Bit.Api.AdminConsole.Models.Response; using Bit.Api.Models.Response; using Bit.Api.Vault.AuthorizationHandlers.Groups; using Bit.Core; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.AdminConsole.Services; using Bit.Core.Context; using Bit.Core.Exceptions; -using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Api.Controllers; +namespace Bit.Api.AdminConsole.Controllers; [Route("organizations/{orgId}/groups")] [Authorize("Application")] diff --git a/src/Api/Controllers/OrganizationUsersController.cs b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs similarity index 98% rename from src/Api/Controllers/OrganizationUsersController.cs rename to src/Api/AdminConsole/Controllers/OrganizationUsersController.cs index 9a1cf63c9f..6c9ecdcf90 100644 --- a/src/Api/Controllers/OrganizationUsersController.cs +++ b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs @@ -1,8 +1,10 @@ -using Bit.Api.Models.Request.Organizations; +using Bit.Api.AdminConsole.Models.Request.Organizations; +using Bit.Api.AdminConsole.Models.Response.Organizations; +using Bit.Api.Models.Request.Organizations; using Bit.Api.Models.Response; -using Bit.Api.Models.Response.Organizations; using Bit.Api.Vault.AuthorizationHandlers.OrganizationUsers; using Bit.Core; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Exceptions; @@ -16,7 +18,7 @@ using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Api.Controllers; +namespace Bit.Api.AdminConsole.Controllers; [Route("organizations/{orgId}/users")] [Authorize("Application")] diff --git a/src/Api/Controllers/OrganizationsController.cs b/src/Api/AdminConsole/Controllers/OrganizationsController.cs similarity index 99% rename from src/Api/Controllers/OrganizationsController.cs rename to src/Api/AdminConsole/Controllers/OrganizationsController.cs index 3c6635b441..0b6b48aa53 100644 --- a/src/Api/Controllers/OrganizationsController.cs +++ b/src/Api/AdminConsole/Controllers/OrganizationsController.cs @@ -1,4 +1,8 @@ using System.Text.Json; +using Bit.Api.AdminConsole.Models.Request; +using Bit.Api.AdminConsole.Models.Request.Organizations; +using Bit.Api.AdminConsole.Models.Response; +using Bit.Api.AdminConsole.Models.Response.Organizations; using Bit.Api.Auth.Models.Request.Accounts; using Bit.Api.Auth.Models.Request.Organizations; using Bit.Api.Auth.Models.Response.Organizations; @@ -6,7 +10,6 @@ using Bit.Api.Models.Request; using Bit.Api.Models.Request.Accounts; using Bit.Api.Models.Request.Organizations; using Bit.Api.Models.Response; -using Bit.Api.Models.Response.Organizations; using Bit.Core; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Repositories; @@ -26,7 +29,7 @@ using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Api.Controllers; +namespace Bit.Api.AdminConsole.Controllers; [Route("organizations")] [Authorize("Application")] diff --git a/src/Api/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Controllers/PoliciesController.cs similarity index 98% rename from src/Api/Controllers/PoliciesController.cs rename to src/Api/AdminConsole/Controllers/PoliciesController.cs index e352e10908..6f93c22def 100644 --- a/src/Api/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Controllers/PoliciesController.cs @@ -1,4 +1,4 @@ -using Bit.Api.Models.Request; +using Bit.Api.AdminConsole.Models.Request; using Bit.Api.Models.Response; using Bit.Core.Context; using Bit.Core.Enums; @@ -12,7 +12,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Mvc; -namespace Bit.Api.Controllers; +namespace Bit.Api.AdminConsole.Controllers; [Route("organizations/{orgId}/policies")] [Authorize("Application")] diff --git a/src/Api/Controllers/ProviderOrganizationsController.cs b/src/Api/AdminConsole/Controllers/ProviderOrganizationsController.cs similarity index 95% rename from src/Api/Controllers/ProviderOrganizationsController.cs rename to src/Api/AdminConsole/Controllers/ProviderOrganizationsController.cs index 6f82832802..1420311d4b 100644 --- a/src/Api/Controllers/ProviderOrganizationsController.cs +++ b/src/Api/AdminConsole/Controllers/ProviderOrganizationsController.cs @@ -1,6 +1,6 @@ -using Bit.Api.Models.Request.Providers; +using Bit.Api.AdminConsole.Models.Request.Providers; +using Bit.Api.AdminConsole.Models.Response.Providers; using Bit.Api.Models.Response; -using Bit.Api.Models.Response.Providers; using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Repositories; @@ -9,7 +9,7 @@ using Bit.Core.Utilities; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Api.Controllers; +namespace Bit.Api.AdminConsole.Controllers; [Route("providers/{providerId:guid}/organizations")] [Authorize("Application")] diff --git a/src/Api/Controllers/ProviderUsersController.cs b/src/Api/AdminConsole/Controllers/ProviderUsersController.cs similarity index 97% rename from src/Api/Controllers/ProviderUsersController.cs rename to src/Api/AdminConsole/Controllers/ProviderUsersController.cs index 9dbcc49e3f..c955e062fa 100644 --- a/src/Api/Controllers/ProviderUsersController.cs +++ b/src/Api/AdminConsole/Controllers/ProviderUsersController.cs @@ -1,6 +1,6 @@ -using Bit.Api.Models.Request.Providers; +using Bit.Api.AdminConsole.Models.Request.Providers; +using Bit.Api.AdminConsole.Models.Response.Providers; using Bit.Api.Models.Response; -using Bit.Api.Models.Response.Providers; using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Models.Business.Provider; @@ -9,7 +9,7 @@ using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Api.Controllers; +namespace Bit.Api.AdminConsole.Controllers; [Route("providers/{providerId:guid}/users")] [Authorize("Application")] diff --git a/src/Api/Controllers/ProvidersController.cs b/src/Api/AdminConsole/Controllers/ProvidersController.cs similarity index 94% rename from src/Api/Controllers/ProvidersController.cs rename to src/Api/AdminConsole/Controllers/ProvidersController.cs index 5daf9ce491..b61a6d4198 100644 --- a/src/Api/Controllers/ProvidersController.cs +++ b/src/Api/AdminConsole/Controllers/ProvidersController.cs @@ -1,5 +1,5 @@ -using Bit.Api.Models.Request.Providers; -using Bit.Api.Models.Response.Providers; +using Bit.Api.AdminConsole.Models.Request.Providers; +using Bit.Api.AdminConsole.Models.Response.Providers; using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Repositories; @@ -8,7 +8,7 @@ using Bit.Core.Settings; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Api.Controllers; +namespace Bit.Api.AdminConsole.Controllers; [Route("providers")] [Authorize("Application")] diff --git a/src/Api/Models/Request/GroupRequestModel.cs b/src/Api/AdminConsole/Models/Request/GroupRequestModel.cs similarity index 86% rename from src/Api/Models/Request/GroupRequestModel.cs rename to src/Api/AdminConsole/Models/Request/GroupRequestModel.cs index aa52a08af2..97c344f95f 100644 --- a/src/Api/Models/Request/GroupRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/GroupRequestModel.cs @@ -1,7 +1,8 @@ using System.ComponentModel.DataAnnotations; -using Bit.Core.Entities; +using Bit.Api.Models.Request; +using Bit.Core.AdminConsole.Entities; -namespace Bit.Api.Models.Request; +namespace Bit.Api.AdminConsole.Models.Request; public class GroupRequestModel { diff --git a/src/Api/Models/Request/Organizations/ImportOrganizationUsersRequestModel.cs b/src/Api/AdminConsole/Models/Request/ImportOrganizationUsersRequestModel.cs similarity index 92% rename from src/Api/Models/Request/Organizations/ImportOrganizationUsersRequestModel.cs rename to src/Api/AdminConsole/Models/Request/ImportOrganizationUsersRequestModel.cs index 3f1e2b2441..48d34c1710 100644 --- a/src/Api/Models/Request/Organizations/ImportOrganizationUsersRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/ImportOrganizationUsersRequestModel.cs @@ -1,7 +1,8 @@ using System.ComponentModel.DataAnnotations; +using Bit.Core.AdminConsole.Models.Business; using Bit.Core.Models.Business; -namespace Bit.Api.Models.Request.Organizations; +namespace Bit.Api.AdminConsole.Models.Request; public class ImportOrganizationUsersRequestModel { @@ -24,7 +25,7 @@ public class ImportOrganizationUsersRequestModel { var importedGroup = new ImportedGroup { - Group = new Core.Entities.Group + Group = new Core.AdminConsole.Entities.Group { OrganizationId = organizationId, Name = Name, diff --git a/src/Api/Models/Request/Accounts/OrganizationApiKeyRequestModel.cs b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationApiKeyRequestModel.cs similarity index 77% rename from src/Api/Models/Request/Accounts/OrganizationApiKeyRequestModel.cs rename to src/Api/AdminConsole/Models/Request/Organizations/OrganizationApiKeyRequestModel.cs index 5e4dff2ef6..af5c4e19d0 100644 --- a/src/Api/Models/Request/Accounts/OrganizationApiKeyRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationApiKeyRequestModel.cs @@ -1,7 +1,7 @@ using Bit.Api.Auth.Models.Request.Accounts; using Bit.Core.Enums; -namespace Bit.Api.Models.Request.Accounts; +namespace Bit.Api.AdminConsole.Models.Request.Organizations; public class OrganizationApiKeyRequestModel : SecretVerificationRequestModel { diff --git a/src/Api/Models/Request/Organizations/OrganizationCreateRequestModel.cs b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationCreateRequestModel.cs similarity index 98% rename from src/Api/Models/Request/Organizations/OrganizationCreateRequestModel.cs rename to src/Api/AdminConsole/Models/Request/Organizations/OrganizationCreateRequestModel.cs index 35151dc4f6..2ea4e8b84e 100644 --- a/src/Api/Models/Request/Organizations/OrganizationCreateRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationCreateRequestModel.cs @@ -4,7 +4,7 @@ using Bit.Core.Enums; using Bit.Core.Models.Business; using Bit.Core.Utilities; -namespace Bit.Api.Models.Request.Organizations; +namespace Bit.Api.AdminConsole.Models.Request.Organizations; public class OrganizationCreateRequestModel : IValidatableObject { diff --git a/src/Api/Models/Request/Organizations/OrganizationKeysRequestModel.cs b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationKeysRequestModel.cs similarity index 96% rename from src/Api/Models/Request/Organizations/OrganizationKeysRequestModel.cs rename to src/Api/AdminConsole/Models/Request/Organizations/OrganizationKeysRequestModel.cs index a22b4eaa6f..3a343caa21 100644 --- a/src/Api/Models/Request/Organizations/OrganizationKeysRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationKeysRequestModel.cs @@ -2,7 +2,7 @@ using Bit.Core.Entities; using Bit.Core.Models.Business; -namespace Bit.Api.Models.Request.Organizations; +namespace Bit.Api.AdminConsole.Models.Request.Organizations; public class OrganizationKeysRequestModel { diff --git a/src/Api/Models/Request/Organizations/OrganizationSeatRequestModel.cs b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationSeatRequestModel.cs similarity index 88% rename from src/Api/Models/Request/Organizations/OrganizationSeatRequestModel.cs rename to src/Api/AdminConsole/Models/Request/Organizations/OrganizationSeatRequestModel.cs index b3849f0a40..309adfceb7 100644 --- a/src/Api/Models/Request/Organizations/OrganizationSeatRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationSeatRequestModel.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Bit.Api.Models.Request.Organizations; +namespace Bit.Api.AdminConsole.Models.Request.Organizations; public class OrganizationSeatRequestModel : IValidatableObject { diff --git a/src/Api/Models/Request/Organizations/OrganizationUpdateRequestModel.cs b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUpdateRequestModel.cs similarity index 94% rename from src/Api/Models/Request/Organizations/OrganizationUpdateRequestModel.cs rename to src/Api/AdminConsole/Models/Request/Organizations/OrganizationUpdateRequestModel.cs index 79983a5549..be526ee6cc 100644 --- a/src/Api/Models/Request/Organizations/OrganizationUpdateRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUpdateRequestModel.cs @@ -3,7 +3,7 @@ using Bit.Core.Entities; using Bit.Core.Models.Data; using Bit.Core.Settings; -namespace Bit.Api.Models.Request.Organizations; +namespace Bit.Api.AdminConsole.Models.Request.Organizations; public class OrganizationUpdateRequestModel { diff --git a/src/Api/Models/Request/Organizations/OrganizationUpgradeRequestModel.cs b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUpgradeRequestModel.cs similarity index 96% rename from src/Api/Models/Request/Organizations/OrganizationUpgradeRequestModel.cs rename to src/Api/AdminConsole/Models/Request/Organizations/OrganizationUpgradeRequestModel.cs index 0c5277ec6e..dae1b1d429 100644 --- a/src/Api/Models/Request/Organizations/OrganizationUpgradeRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUpgradeRequestModel.cs @@ -2,7 +2,7 @@ using Bit.Core.Enums; using Bit.Core.Models.Business; -namespace Bit.Api.Models.Request.Organizations; +namespace Bit.Api.AdminConsole.Models.Request.Organizations; public class OrganizationUpgradeRequestModel { diff --git a/src/Api/Models/Request/Organizations/OrganizationUserRequestModels.cs b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUserRequestModels.cs similarity index 97% rename from src/Api/Models/Request/Organizations/OrganizationUserRequestModels.cs rename to src/Api/AdminConsole/Models/Request/Organizations/OrganizationUserRequestModels.cs index 1206d3c973..bf10d85c0b 100644 --- a/src/Api/Models/Request/Organizations/OrganizationUserRequestModels.cs +++ b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUserRequestModels.cs @@ -1,11 +1,12 @@ using System.ComponentModel.DataAnnotations; +using Bit.Api.Models.Request; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Utilities; -namespace Bit.Api.Models.Request.Organizations; +namespace Bit.Api.AdminConsole.Models.Request.Organizations; public class OrganizationUserInviteRequestModel { diff --git a/src/Api/Models/Request/PolicyRequestModel.cs b/src/Api/AdminConsole/Models/Request/PolicyRequestModel.cs similarity index 94% rename from src/Api/Models/Request/PolicyRequestModel.cs rename to src/Api/AdminConsole/Models/Request/PolicyRequestModel.cs index bc303cd40f..b3ba3a6bca 100644 --- a/src/Api/Models/Request/PolicyRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/PolicyRequestModel.cs @@ -3,7 +3,7 @@ using System.Text.Json; using Bit.Core.Entities; using Bit.Core.Enums; -namespace Bit.Api.Models.Request; +namespace Bit.Api.AdminConsole.Models.Request; public class PolicyRequestModel { diff --git a/src/Api/Models/Request/Providers/ProviderOrganizationAddRequestModel.cs b/src/Api/AdminConsole/Models/Request/Providers/ProviderOrganizationAddRequestModel.cs similarity index 79% rename from src/Api/Models/Request/Providers/ProviderOrganizationAddRequestModel.cs rename to src/Api/AdminConsole/Models/Request/Providers/ProviderOrganizationAddRequestModel.cs index b6ea8759e0..207d84b787 100644 --- a/src/Api/Models/Request/Providers/ProviderOrganizationAddRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/Providers/ProviderOrganizationAddRequestModel.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Bit.Api.Models.Request.Providers; +namespace Bit.Api.AdminConsole.Models.Request.Providers; public class ProviderOrganizationAddRequestModel { diff --git a/src/Api/Models/Request/Providers/ProviderOrganizationCreateRequestModel.cs b/src/Api/AdminConsole/Models/Request/Providers/ProviderOrganizationCreateRequestModel.cs similarity index 75% rename from src/Api/Models/Request/Providers/ProviderOrganizationCreateRequestModel.cs rename to src/Api/AdminConsole/Models/Request/Providers/ProviderOrganizationCreateRequestModel.cs index 7fead717be..bf75c611e2 100644 --- a/src/Api/Models/Request/Providers/ProviderOrganizationCreateRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/Providers/ProviderOrganizationCreateRequestModel.cs @@ -1,8 +1,8 @@ using System.ComponentModel.DataAnnotations; -using Bit.Api.Models.Request.Organizations; +using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Core.Utilities; -namespace Bit.Api.Models.Request.Providers; +namespace Bit.Api.AdminConsole.Models.Request.Providers; public class ProviderOrganizationCreateRequestModel { diff --git a/src/Api/Models/Request/Providers/ProviderSetupRequestModel.cs b/src/Api/AdminConsole/Models/Request/Providers/ProviderSetupRequestModel.cs similarity index 92% rename from src/Api/Models/Request/Providers/ProviderSetupRequestModel.cs rename to src/Api/AdminConsole/Models/Request/Providers/ProviderSetupRequestModel.cs index 51191f947c..2c5e4c65bb 100644 --- a/src/Api/Models/Request/Providers/ProviderSetupRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/Providers/ProviderSetupRequestModel.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using Bit.Core.Entities.Provider; -namespace Bit.Api.Models.Request.Providers; +namespace Bit.Api.AdminConsole.Models.Request.Providers; public class ProviderSetupRequestModel { diff --git a/src/Api/Models/Request/Providers/ProviderUpdateRequestModel.cs b/src/Api/AdminConsole/Models/Request/Providers/ProviderUpdateRequestModel.cs similarity index 93% rename from src/Api/Models/Request/Providers/ProviderUpdateRequestModel.cs rename to src/Api/AdminConsole/Models/Request/Providers/ProviderUpdateRequestModel.cs index ceec796dc4..58b4fc0f51 100644 --- a/src/Api/Models/Request/Providers/ProviderUpdateRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/Providers/ProviderUpdateRequestModel.cs @@ -2,7 +2,7 @@ using Bit.Core.Entities.Provider; using Bit.Core.Settings; -namespace Bit.Api.Models.Request.Providers; +namespace Bit.Api.AdminConsole.Models.Request.Providers; public class ProviderUpdateRequestModel { diff --git a/src/Api/Models/Request/Providers/ProviderUserRequestModels.cs b/src/Api/AdminConsole/Models/Request/Providers/ProviderUserRequestModels.cs similarity index 95% rename from src/Api/Models/Request/Providers/ProviderUserRequestModels.cs rename to src/Api/AdminConsole/Models/Request/Providers/ProviderUserRequestModels.cs index 9c451d8adc..ec112edc4a 100644 --- a/src/Api/Models/Request/Providers/ProviderUserRequestModels.cs +++ b/src/Api/AdminConsole/Models/Request/Providers/ProviderUserRequestModels.cs @@ -3,7 +3,7 @@ using Bit.Core.Entities.Provider; using Bit.Core.Enums.Provider; using Bit.Core.Utilities; -namespace Bit.Api.Models.Request.Providers; +namespace Bit.Api.AdminConsole.Models.Request.Providers; public class ProviderUserInviteRequestModel { diff --git a/src/Api/Models/Response/GroupResponseModel.cs b/src/Api/AdminConsole/Models/Response/GroupResponseModel.cs similarity index 89% rename from src/Api/Models/Response/GroupResponseModel.cs rename to src/Api/AdminConsole/Models/Response/GroupResponseModel.cs index 83dffb7552..b5d00fca98 100644 --- a/src/Api/Models/Response/GroupResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/GroupResponseModel.cs @@ -1,8 +1,9 @@ -using Bit.Core.Entities; +using Bit.Api.Models.Response; +using Bit.Core.AdminConsole.Entities; using Bit.Core.Models.Api; using Bit.Core.Models.Data; -namespace Bit.Api.Models.Response; +namespace Bit.Api.AdminConsole.Models.Response; public class GroupResponseModel : ResponseModel { diff --git a/src/Api/Models/Response/Organizations/OrganizationApiKeyInformationResponseModel.cs b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationApiKeyInformationResponseModel.cs similarity index 87% rename from src/Api/Models/Response/Organizations/OrganizationApiKeyInformationResponseModel.cs rename to src/Api/AdminConsole/Models/Response/Organizations/OrganizationApiKeyInformationResponseModel.cs index a25cb89355..ba1b290db7 100644 --- a/src/Api/Models/Response/Organizations/OrganizationApiKeyInformationResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationApiKeyInformationResponseModel.cs @@ -2,7 +2,7 @@ using Bit.Core.Enums; using Bit.Core.Models.Api; -namespace Bit.Api.Models.Response.Organizations; +namespace Bit.Api.AdminConsole.Models.Response.Organizations; public class OrganizationApiKeyInformation : ResponseModel { diff --git a/src/Api/Models/Response/Organizations/OrganizationAutoEnrollStatusResponseModel.cs b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationAutoEnrollStatusResponseModel.cs similarity index 86% rename from src/Api/Models/Response/Organizations/OrganizationAutoEnrollStatusResponseModel.cs rename to src/Api/AdminConsole/Models/Response/Organizations/OrganizationAutoEnrollStatusResponseModel.cs index 2318d3ff05..c853b78a6c 100644 --- a/src/Api/Models/Response/Organizations/OrganizationAutoEnrollStatusResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationAutoEnrollStatusResponseModel.cs @@ -1,6 +1,6 @@ using Bit.Core.Models.Api; -namespace Bit.Api.Models.Response.Organizations; +namespace Bit.Api.AdminConsole.Models.Response.Organizations; public class OrganizationAutoEnrollStatusResponseModel : ResponseModel { diff --git a/src/Api/Models/Response/Organizations/OrganizationKeysResponseModel.cs b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationKeysResponseModel.cs similarity index 88% rename from src/Api/Models/Response/Organizations/OrganizationKeysResponseModel.cs rename to src/Api/AdminConsole/Models/Response/Organizations/OrganizationKeysResponseModel.cs index 35c2f77e7d..a2e4fe058d 100644 --- a/src/Api/Models/Response/Organizations/OrganizationKeysResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationKeysResponseModel.cs @@ -1,7 +1,7 @@ using Bit.Core.Entities; using Bit.Core.Models.Api; -namespace Bit.Api.Models.Response.Organizations; +namespace Bit.Api.AdminConsole.Models.Response.Organizations; public class OrganizationKeysResponseModel : ResponseModel { diff --git a/src/Api/Models/Response/Organizations/OrganizationPublicKeyResponseModel.cs b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationPublicKeyResponseModel.cs similarity index 87% rename from src/Api/Models/Response/Organizations/OrganizationPublicKeyResponseModel.cs rename to src/Api/AdminConsole/Models/Response/Organizations/OrganizationPublicKeyResponseModel.cs index 0451433e1b..dc12479618 100644 --- a/src/Api/Models/Response/Organizations/OrganizationPublicKeyResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationPublicKeyResponseModel.cs @@ -1,7 +1,7 @@ using Bit.Core.Entities; using Bit.Core.Models.Api; -namespace Bit.Api.Models.Response.Organizations; +namespace Bit.Api.AdminConsole.Models.Response.Organizations; public class OrganizationPublicKeyResponseModel : ResponseModel { diff --git a/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs similarity index 95% rename from src/Api/Models/Response/Organizations/OrganizationResponseModel.cs rename to src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs index b1d50da7d6..82b17c0328 100644 --- a/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs @@ -1,11 +1,12 @@ -using Bit.Core.Entities; +using Bit.Api.Models.Response; +using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Api; using Bit.Core.Models.Business; using Bit.Core.Utilities; using Constants = Bit.Core.Constants; -namespace Bit.Api.Models.Response.Organizations; +namespace Bit.Api.AdminConsole.Models.Response.Organizations; public class OrganizationResponseModel : ResponseModel { @@ -113,7 +114,7 @@ public class OrganizationSubscriptionResponseModel : OrganizationResponseModel { Subscription = subscription.Subscription != null ? new BillingSubscription(subscription.Subscription) : null; UpcomingInvoice = subscription.UpcomingInvoice != null ? new BillingSubscriptionUpcomingInvoice(subscription.UpcomingInvoice) : null; - Discount = subscription.Discount != null ? new BillingCustomerDiscount(subscription.Discount) : null; + CustomerDiscount = subscription.CustomerDiscount != null ? new BillingCustomerDiscount(subscription.CustomerDiscount) : null; Expiration = DateTime.UtcNow.AddYears(1); // Not used, so just give it a value. if (hideSensitiveData) @@ -144,7 +145,7 @@ public class OrganizationSubscriptionResponseModel : OrganizationResponseModel public string StorageName { get; set; } public double? StorageGb { get; set; } - public BillingCustomerDiscount Discount { get; set; } + public BillingCustomerDiscount CustomerDiscount { get; set; } public BillingSubscription Subscription { get; set; } public BillingSubscriptionUpcomingInvoice UpcomingInvoice { get; set; } diff --git a/src/Api/Models/Response/Organizations/OrganizationUserResponseModel.cs b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationUserResponseModel.cs similarity index 98% rename from src/Api/Models/Response/Organizations/OrganizationUserResponseModel.cs rename to src/Api/AdminConsole/Models/Response/Organizations/OrganizationUserResponseModel.cs index 06a39f3f90..ee1d790fa6 100644 --- a/src/Api/Models/Response/Organizations/OrganizationUserResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationUserResponseModel.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using Bit.Api.Models.Response; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Api; @@ -6,7 +7,7 @@ using Bit.Core.Models.Data; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Utilities; -namespace Bit.Api.Models.Response.Organizations; +namespace Bit.Api.AdminConsole.Models.Response.Organizations; public class OrganizationUserResponseModel : ResponseModel { diff --git a/src/Api/Models/Response/ProfileOrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs similarity index 99% rename from src/Api/Models/Response/ProfileOrganizationResponseModel.cs rename to src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs index f75baccc67..ce6ac21c02 100644 --- a/src/Api/Models/Response/ProfileOrganizationResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs @@ -7,7 +7,7 @@ using Bit.Core.Models.Data; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Utilities; -namespace Bit.Api.Models.Response; +namespace Bit.Api.AdminConsole.Models.Response; public class ProfileOrganizationResponseModel : ResponseModel { diff --git a/src/Api/Models/Response/ProfileProviderOrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs similarity index 97% rename from src/Api/Models/Response/ProfileProviderOrganizationResponseModel.cs rename to src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs index 9a66de2e1c..b79a325601 100644 --- a/src/Api/Models/Response/ProfileProviderOrganizationResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/ProfileProviderOrganizationResponseModel.cs @@ -2,7 +2,7 @@ using Bit.Core.Models.Data; using Bit.Core.Utilities; -namespace Bit.Api.Models.Response; +namespace Bit.Api.AdminConsole.Models.Response; public class ProfileProviderOrganizationResponseModel : ProfileOrganizationResponseModel { diff --git a/src/Api/Models/Response/Providers/ProfileProviderResponseModel.cs b/src/Api/AdminConsole/Models/Response/Providers/ProfileProviderResponseModel.cs similarity index 94% rename from src/Api/Models/Response/Providers/ProfileProviderResponseModel.cs rename to src/Api/AdminConsole/Models/Response/Providers/ProfileProviderResponseModel.cs index e2d494dad1..ac725618b5 100644 --- a/src/Api/Models/Response/Providers/ProfileProviderResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Providers/ProfileProviderResponseModel.cs @@ -3,7 +3,7 @@ using Bit.Core.Models.Api; using Bit.Core.Models.Data; using Bit.Core.Utilities; -namespace Bit.Api.Models.Response.Providers; +namespace Bit.Api.AdminConsole.Models.Response.Providers; public class ProfileProviderResponseModel : ResponseModel { diff --git a/src/Api/Models/Response/Providers/ProviderOrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/Providers/ProviderOrganizationResponseModel.cs similarity index 97% rename from src/Api/Models/Response/Providers/ProviderOrganizationResponseModel.cs rename to src/Api/AdminConsole/Models/Response/Providers/ProviderOrganizationResponseModel.cs index 9bc7d52dc6..a2e9d9f4a2 100644 --- a/src/Api/Models/Response/Providers/ProviderOrganizationResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Providers/ProviderOrganizationResponseModel.cs @@ -2,7 +2,7 @@ using Bit.Core.Models.Api; using Bit.Core.Models.Data; -namespace Bit.Api.Models.Response.Providers; +namespace Bit.Api.AdminConsole.Models.Response.Providers; public class ProviderOrganizationResponseModel : ResponseModel { diff --git a/src/Api/Models/Response/Providers/ProviderResponseModel.cs b/src/Api/AdminConsole/Models/Response/Providers/ProviderResponseModel.cs similarity index 95% rename from src/Api/Models/Response/Providers/ProviderResponseModel.cs rename to src/Api/AdminConsole/Models/Response/Providers/ProviderResponseModel.cs index ce62fdaa68..5e26d1d4be 100644 --- a/src/Api/Models/Response/Providers/ProviderResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Providers/ProviderResponseModel.cs @@ -1,7 +1,7 @@ using Bit.Core.Entities.Provider; using Bit.Core.Models.Api; -namespace Bit.Api.Models.Response.Providers; +namespace Bit.Api.AdminConsole.Models.Response.Providers; public class ProviderResponseModel : ResponseModel { diff --git a/src/Api/Models/Response/Providers/ProviderUserResponseModel.cs b/src/Api/AdminConsole/Models/Response/Providers/ProviderUserResponseModel.cs similarity index 97% rename from src/Api/Models/Response/Providers/ProviderUserResponseModel.cs rename to src/Api/AdminConsole/Models/Response/Providers/ProviderUserResponseModel.cs index b47b9cdd2b..1008022885 100644 --- a/src/Api/Models/Response/Providers/ProviderUserResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Providers/ProviderUserResponseModel.cs @@ -4,7 +4,7 @@ using Bit.Core.Models.Api; using Bit.Core.Models.Data; using Bit.Core.Utilities; -namespace Bit.Api.Models.Response.Providers; +namespace Bit.Api.AdminConsole.Models.Response.Providers; public class ProviderUserResponseModel : ResponseModel { diff --git a/src/Api/Public/Controllers/GroupsController.cs b/src/Api/AdminConsole/Public/Controllers/GroupsController.cs similarity index 96% rename from src/Api/Public/Controllers/GroupsController.cs rename to src/Api/AdminConsole/Public/Controllers/GroupsController.cs index 7080405451..01c38a084f 100644 --- a/src/Api/Public/Controllers/GroupsController.cs +++ b/src/Api/AdminConsole/Public/Controllers/GroupsController.cs @@ -1,13 +1,15 @@ using System.Net; -using Bit.Api.Models.Public.Request; +using Bit.Api.AdminConsole.Public.Models.Request; +using Bit.Api.AdminConsole.Public.Models.Response; using Bit.Api.Models.Public.Response; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Context; -using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.Repositories; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Api.Public.Controllers; +namespace Bit.Api.AdminConsole.Public.Controllers; [Route("public/groups")] [Authorize("Organization")] diff --git a/src/Api/Public/Controllers/MembersController.cs b/src/Api/AdminConsole/Public/Controllers/MembersController.cs similarity index 98% rename from src/Api/Public/Controllers/MembersController.cs rename to src/Api/AdminConsole/Public/Controllers/MembersController.cs index 74dd4b83e1..52cf952f7b 100644 --- a/src/Api/Public/Controllers/MembersController.cs +++ b/src/Api/AdminConsole/Public/Controllers/MembersController.cs @@ -1,6 +1,8 @@ using System.Net; -using Bit.Api.Models.Public.Request; +using Bit.Api.AdminConsole.Public.Models.Request; +using Bit.Api.AdminConsole.Public.Models.Response; using Bit.Api.Models.Public.Response; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Context; using Bit.Core.Models.Business; using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces; @@ -9,7 +11,7 @@ using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Api.Public.Controllers; +namespace Bit.Api.AdminConsole.Public.Controllers; [Route("public/members")] [Authorize("Organization")] diff --git a/src/Api/Public/Controllers/OrganizationController.cs b/src/Api/AdminConsole/Public/Controllers/OrganizationController.cs similarity index 93% rename from src/Api/Public/Controllers/OrganizationController.cs rename to src/Api/AdminConsole/Public/Controllers/OrganizationController.cs index ce0683b95d..65dc15e771 100644 --- a/src/Api/Public/Controllers/OrganizationController.cs +++ b/src/Api/AdminConsole/Public/Controllers/OrganizationController.cs @@ -1,5 +1,6 @@ using System.Net; -using Bit.Api.Models.Public.Request; +using Bit.Api.AdminConsole.Public.Models.Request; +using Bit.Api.AdminConsole.Public.Models.Response; using Bit.Api.Models.Public.Response; using Bit.Core.Context; using Bit.Core.Exceptions; @@ -8,7 +9,7 @@ using Bit.Core.Settings; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Api.Public.Controllers; +namespace Bit.Api.AdminConsole.Public.Controllers; [Route("public/organization")] [Authorize("Organization")] diff --git a/src/Api/Public/Controllers/PoliciesController.cs b/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs similarity index 96% rename from src/Api/Public/Controllers/PoliciesController.cs rename to src/Api/AdminConsole/Public/Controllers/PoliciesController.cs index b208938ed7..bcabb969c6 100644 --- a/src/Api/Public/Controllers/PoliciesController.cs +++ b/src/Api/AdminConsole/Public/Controllers/PoliciesController.cs @@ -1,5 +1,6 @@ using System.Net; -using Bit.Api.Models.Public.Request; +using Bit.Api.AdminConsole.Public.Models.Request; +using Bit.Api.AdminConsole.Public.Models.Response; using Bit.Api.Models.Public.Response; using Bit.Core.Context; using Bit.Core.Enums; @@ -8,7 +9,7 @@ using Bit.Core.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Bit.Api.Public.Controllers; +namespace Bit.Api.AdminConsole.Public.Controllers; [Route("public/policies")] [Authorize("Organization")] diff --git a/src/Api/Models/Public/GroupBaseModel.cs b/src/Api/AdminConsole/Public/Models/GroupBaseModel.cs similarity index 94% rename from src/Api/Models/Public/GroupBaseModel.cs rename to src/Api/AdminConsole/Public/Models/GroupBaseModel.cs index 2b09e2952b..ff1813aff4 100644 --- a/src/Api/Models/Public/GroupBaseModel.cs +++ b/src/Api/AdminConsole/Public/Models/GroupBaseModel.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Bit.Api.Models.Public; +namespace Bit.Api.AdminConsole.Public.Models; public abstract class GroupBaseModel { diff --git a/src/Api/Models/Public/MemberBaseModel.cs b/src/Api/AdminConsole/Public/Models/MemberBaseModel.cs similarity index 97% rename from src/Api/Models/Public/MemberBaseModel.cs rename to src/Api/AdminConsole/Public/Models/MemberBaseModel.cs index af57d80645..d7eaf98509 100644 --- a/src/Api/Models/Public/MemberBaseModel.cs +++ b/src/Api/AdminConsole/Public/Models/MemberBaseModel.cs @@ -3,7 +3,7 @@ using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Data.Organizations.OrganizationUsers; -namespace Bit.Api.Models.Public; +namespace Bit.Api.AdminConsole.Public.Models; public abstract class MemberBaseModel { diff --git a/src/Api/Models/Public/PolicyBaseModel.cs b/src/Api/AdminConsole/Public/Models/PolicyBaseModel.cs similarity index 88% rename from src/Api/Models/Public/PolicyBaseModel.cs rename to src/Api/AdminConsole/Public/Models/PolicyBaseModel.cs index 2ad8e76005..f474d87ec9 100644 --- a/src/Api/Models/Public/PolicyBaseModel.cs +++ b/src/Api/AdminConsole/Public/Models/PolicyBaseModel.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Bit.Api.Models.Public; +namespace Bit.Api.AdminConsole.Public.Models; public abstract class PolicyBaseModel { diff --git a/src/Api/Models/Public/Request/GroupCreateUpdateRequestModel.cs b/src/Api/AdminConsole/Public/Models/Request/GroupCreateUpdateRequestModel.cs similarity index 87% rename from src/Api/Models/Public/Request/GroupCreateUpdateRequestModel.cs rename to src/Api/AdminConsole/Public/Models/Request/GroupCreateUpdateRequestModel.cs index aa100b6a7f..01850003d7 100644 --- a/src/Api/Models/Public/Request/GroupCreateUpdateRequestModel.cs +++ b/src/Api/AdminConsole/Public/Models/Request/GroupCreateUpdateRequestModel.cs @@ -1,7 +1,7 @@ using Bit.Api.Auth.Models.Public.Request; -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; -namespace Bit.Api.Models.Public.Request; +namespace Bit.Api.AdminConsole.Public.Models.Request; public class GroupCreateUpdateRequestModel : GroupBaseModel { diff --git a/src/Api/Models/Public/Request/MemberCreateRequestModel.cs b/src/Api/AdminConsole/Public/Models/Request/MemberCreateRequestModel.cs similarity index 90% rename from src/Api/Models/Public/Request/MemberCreateRequestModel.cs rename to src/Api/AdminConsole/Public/Models/Request/MemberCreateRequestModel.cs index 447434e470..cdbc99d0d1 100644 --- a/src/Api/Models/Public/Request/MemberCreateRequestModel.cs +++ b/src/Api/AdminConsole/Public/Models/Request/MemberCreateRequestModel.cs @@ -2,7 +2,7 @@ using Bit.Core.Entities; using Bit.Core.Utilities; -namespace Bit.Api.Models.Public.Request; +namespace Bit.Api.AdminConsole.Public.Models.Request; public class MemberCreateRequestModel : MemberUpdateRequestModel { diff --git a/src/Api/Models/Public/Request/MemberUpdateRequestModel.cs b/src/Api/AdminConsole/Public/Models/Request/MemberUpdateRequestModel.cs similarity index 93% rename from src/Api/Models/Public/Request/MemberUpdateRequestModel.cs rename to src/Api/AdminConsole/Public/Models/Request/MemberUpdateRequestModel.cs index 733e1285e6..18aced4371 100644 --- a/src/Api/Models/Public/Request/MemberUpdateRequestModel.cs +++ b/src/Api/AdminConsole/Public/Models/Request/MemberUpdateRequestModel.cs @@ -1,7 +1,7 @@ using Bit.Api.Auth.Models.Public.Request; using Bit.Core.Entities; -namespace Bit.Api.Models.Public.Request; +namespace Bit.Api.AdminConsole.Public.Models.Request; public class MemberUpdateRequestModel : MemberBaseModel { diff --git a/src/Api/Models/Public/Request/OrganizationImportRequestModel.cs b/src/Api/AdminConsole/Public/Models/Request/OrganizationImportRequestModel.cs similarity index 96% rename from src/Api/Models/Public/Request/OrganizationImportRequestModel.cs rename to src/Api/AdminConsole/Public/Models/Request/OrganizationImportRequestModel.cs index 70bf649a25..2adda81e49 100644 --- a/src/Api/Models/Public/Request/OrganizationImportRequestModel.cs +++ b/src/Api/AdminConsole/Public/Models/Request/OrganizationImportRequestModel.cs @@ -1,10 +1,11 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Models.Business; using Bit.Core.Models.Business; using Bit.Core.Utilities; -namespace Bit.Api.Models.Public.Request; +namespace Bit.Api.AdminConsole.Public.Models.Request; public class OrganizationImportRequestModel { diff --git a/src/Api/Models/Public/Request/PolicyUpdateRequestModel.cs b/src/Api/AdminConsole/Public/Models/Request/PolicyUpdateRequestModel.cs similarity index 90% rename from src/Api/Models/Public/Request/PolicyUpdateRequestModel.cs rename to src/Api/AdminConsole/Public/Models/Request/PolicyUpdateRequestModel.cs index 251b9358d1..1866da6eca 100644 --- a/src/Api/Models/Public/Request/PolicyUpdateRequestModel.cs +++ b/src/Api/AdminConsole/Public/Models/Request/PolicyUpdateRequestModel.cs @@ -1,7 +1,7 @@ using System.Text.Json; using Bit.Core.Entities; -namespace Bit.Api.Models.Public.Request; +namespace Bit.Api.AdminConsole.Public.Models.Request; public class PolicyUpdateRequestModel : PolicyBaseModel { diff --git a/src/Api/Models/Public/Request/UpdateGroupIdsRequestModel.cs b/src/Api/AdminConsole/Public/Models/Request/UpdateGroupIdsRequestModel.cs similarity index 77% rename from src/Api/Models/Public/Request/UpdateGroupIdsRequestModel.cs rename to src/Api/AdminConsole/Public/Models/Request/UpdateGroupIdsRequestModel.cs index 7a818e5bbc..c55be36fff 100644 --- a/src/Api/Models/Public/Request/UpdateGroupIdsRequestModel.cs +++ b/src/Api/AdminConsole/Public/Models/Request/UpdateGroupIdsRequestModel.cs @@ -1,4 +1,4 @@ -namespace Bit.Api.Models.Public.Request; +namespace Bit.Api.AdminConsole.Public.Models.Request; public class UpdateGroupIdsRequestModel { diff --git a/src/Api/Models/Public/Request/UpdateMemberIdsRequestModel.cs b/src/Api/AdminConsole/Public/Models/Request/UpdateMemberIdsRequestModel.cs similarity index 78% rename from src/Api/Models/Public/Request/UpdateMemberIdsRequestModel.cs rename to src/Api/AdminConsole/Public/Models/Request/UpdateMemberIdsRequestModel.cs index 87a2418318..4124719929 100644 --- a/src/Api/Models/Public/Request/UpdateMemberIdsRequestModel.cs +++ b/src/Api/AdminConsole/Public/Models/Request/UpdateMemberIdsRequestModel.cs @@ -1,4 +1,4 @@ -namespace Bit.Api.Models.Public.Request; +namespace Bit.Api.AdminConsole.Public.Models.Request; public class UpdateMemberIdsRequestModel { diff --git a/src/Api/Models/Public/Response/GroupResponseModel.cs b/src/Api/AdminConsole/Public/Models/Response/GroupResponseModel.cs similarity index 90% rename from src/Api/Models/Public/Response/GroupResponseModel.cs rename to src/Api/AdminConsole/Public/Models/Response/GroupResponseModel.cs index 82c053c665..b1100ef02c 100644 --- a/src/Api/Models/Public/Response/GroupResponseModel.cs +++ b/src/Api/AdminConsole/Public/Models/Response/GroupResponseModel.cs @@ -1,9 +1,10 @@ using System.ComponentModel.DataAnnotations; using Bit.Api.Auth.Models.Public.Response; -using Bit.Core.Entities; +using Bit.Api.Models.Public.Response; +using Bit.Core.AdminConsole.Entities; using Bit.Core.Models.Data; -namespace Bit.Api.Models.Public.Response; +namespace Bit.Api.AdminConsole.Public.Models.Response; /// /// A user group. diff --git a/src/Api/Models/Public/Response/MemberResponseModel.cs b/src/Api/AdminConsole/Public/Models/Response/MemberResponseModel.cs similarity index 97% rename from src/Api/Models/Public/Response/MemberResponseModel.cs rename to src/Api/AdminConsole/Public/Models/Response/MemberResponseModel.cs index b2e094cfa6..4f2ff1c178 100644 --- a/src/Api/Models/Public/Response/MemberResponseModel.cs +++ b/src/Api/AdminConsole/Public/Models/Response/MemberResponseModel.cs @@ -1,11 +1,12 @@ using System.ComponentModel.DataAnnotations; using Bit.Api.Auth.Models.Public.Response; +using Bit.Api.Models.Public.Response; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Data; using Bit.Core.Models.Data.Organizations.OrganizationUsers; -namespace Bit.Api.Models.Public.Response; +namespace Bit.Api.AdminConsole.Public.Models.Response; /// /// An organization member. diff --git a/src/Api/Models/Public/Response/PolicyResponseModel.cs b/src/Api/AdminConsole/Public/Models/Response/PolicyResponseModel.cs similarity index 92% rename from src/Api/Models/Public/Response/PolicyResponseModel.cs rename to src/Api/AdminConsole/Public/Models/Response/PolicyResponseModel.cs index b30c283229..3b196874d5 100644 --- a/src/Api/Models/Public/Response/PolicyResponseModel.cs +++ b/src/Api/AdminConsole/Public/Models/Response/PolicyResponseModel.cs @@ -1,9 +1,10 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json; +using Bit.Api.Models.Public.Response; using Bit.Core.Entities; using Bit.Core.Enums; -namespace Bit.Api.Models.Public.Response; +namespace Bit.Api.AdminConsole.Public.Models.Response; /// /// A policy. diff --git a/src/Api/Auth/Controllers/EmergencyAccessController.cs b/src/Api/Auth/Controllers/EmergencyAccessController.cs index cb58e8dfec..ec2a9fc6b7 100644 --- a/src/Api/Auth/Controllers/EmergencyAccessController.cs +++ b/src/Api/Auth/Controllers/EmergencyAccessController.cs @@ -1,6 +1,6 @@ -using Bit.Api.Auth.Models.Request; +using Bit.Api.AdminConsole.Models.Request.Organizations; +using Bit.Api.Auth.Models.Request; using Bit.Api.Auth.Models.Response; -using Bit.Api.Models.Request.Organizations; using Bit.Api.Models.Response; using Bit.Api.Vault.Models.Response; using Bit.Core.Auth.Services; diff --git a/src/Api/Controllers/AccountsController.cs b/src/Api/Controllers/AccountsController.cs index d199c223de..7c36838461 100644 --- a/src/Api/Controllers/AccountsController.cs +++ b/src/Api/Controllers/AccountsController.cs @@ -1,4 +1,5 @@ -using Bit.Api.Auth.Models.Request.Accounts; +using Bit.Api.AdminConsole.Models.Response; +using Bit.Api.Auth.Models.Request.Accounts; using Bit.Api.Models.Request; using Bit.Api.Models.Request.Accounts; using Bit.Api.Models.Response; diff --git a/src/Api/Controllers/EventsController.cs b/src/Api/Controllers/EventsController.cs index 585568db61..4cf3b83dc5 100644 --- a/src/Api/Controllers/EventsController.cs +++ b/src/Api/Controllers/EventsController.cs @@ -1,4 +1,5 @@ using Bit.Api.Models.Response; +using Bit.Api.Utilities; using Bit.Core.Context; using Bit.Core.Exceptions; using Bit.Core.Models.Data; @@ -41,7 +42,7 @@ public class EventsController : Controller public async Task> GetUser( [FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null) { - var dateRange = GetDateRange(start, end); + var dateRange = ApiHelpers.GetDateRange(start, end); var userId = _userService.GetProperUserId(User).Value; var result = await _eventRepository.GetManyByUserAsync(userId, dateRange.Item1, dateRange.Item2, new PageOptions { ContinuationToken = continuationToken }); @@ -75,7 +76,7 @@ public class EventsController : Controller throw new NotFoundException(); } - var dateRange = GetDateRange(start, end); + var dateRange = ApiHelpers.GetDateRange(start, end); var result = await _eventRepository.GetManyByCipherAsync(cipher, dateRange.Item1, dateRange.Item2, new PageOptions { ContinuationToken = continuationToken }); var responses = result.Data.Select(e => new EventResponseModel(e)); @@ -92,7 +93,7 @@ public class EventsController : Controller throw new NotFoundException(); } - var dateRange = GetDateRange(start, end); + var dateRange = ApiHelpers.GetDateRange(start, end); var result = await _eventRepository.GetManyByOrganizationAsync(orgId, dateRange.Item1, dateRange.Item2, new PageOptions { ContinuationToken = continuationToken }); var responses = result.Data.Select(e => new EventResponseModel(e)); @@ -110,7 +111,7 @@ public class EventsController : Controller throw new NotFoundException(); } - var dateRange = GetDateRange(start, end); + var dateRange = ApiHelpers.GetDateRange(start, end); var result = await _eventRepository.GetManyByOrganizationActingUserAsync(organizationUser.OrganizationId, organizationUser.UserId.Value, dateRange.Item1, dateRange.Item2, new PageOptions { ContinuationToken = continuationToken }); @@ -127,7 +128,7 @@ public class EventsController : Controller throw new NotFoundException(); } - var dateRange = GetDateRange(start, end); + var dateRange = ApiHelpers.GetDateRange(start, end); var result = await _eventRepository.GetManyByProviderAsync(providerId, dateRange.Item1, dateRange.Item2, new PageOptions { ContinuationToken = continuationToken }); var responses = result.Data.Select(e => new EventResponseModel(e)); @@ -145,33 +146,11 @@ public class EventsController : Controller throw new NotFoundException(); } - var dateRange = GetDateRange(start, end); + var dateRange = ApiHelpers.GetDateRange(start, end); var result = await _eventRepository.GetManyByProviderActingUserAsync(providerUser.ProviderId, providerUser.UserId.Value, dateRange.Item1, dateRange.Item2, new PageOptions { ContinuationToken = continuationToken }); var responses = result.Data.Select(e => new EventResponseModel(e)); return new ListResponseModel(responses, result.ContinuationToken); } - - private Tuple GetDateRange(DateTime? start, DateTime? end) - { - if (!end.HasValue || !start.HasValue) - { - end = DateTime.UtcNow.Date.AddDays(1).AddMilliseconds(-1); - start = DateTime.UtcNow.Date.AddDays(-30); - } - else if (start.Value > end.Value) - { - var newEnd = start; - start = end; - end = newEnd; - } - - if ((end.Value - start.Value) > TimeSpan.FromDays(367)) - { - throw new BadRequestException("Range too large."); - } - - return new Tuple(start.Value, end.Value); - } } diff --git a/src/Api/Controllers/OrganizationConnectionsController.cs b/src/Api/Controllers/OrganizationConnectionsController.cs index 4c04a24973..e9ea31c8ff 100644 --- a/src/Api/Controllers/OrganizationConnectionsController.cs +++ b/src/Api/Controllers/OrganizationConnectionsController.cs @@ -78,7 +78,12 @@ public class OrganizationConnectionsController : Controller [HttpPut("{organizationConnectionId}")] public async Task UpdateConnection(Guid organizationConnectionId, [FromBody] OrganizationConnectionRequestModel model) { - var existingOrganizationConnection = await _organizationConnectionRepository.GetByIdAsync(organizationConnectionId); + if (model == null) + { + throw new NotFoundException(); + } + + var existingOrganizationConnection = await _organizationConnectionRepository.GetByIdOrganizationIdAsync(organizationConnectionId, model.OrganizationId); if (existingOrganizationConnection == null) { throw new NotFoundException(); diff --git a/src/Api/Controllers/OrganizationDomainController.cs b/src/Api/Controllers/OrganizationDomainController.cs index 23e4f51bc7..60abdad7d4 100644 --- a/src/Api/Controllers/OrganizationDomainController.cs +++ b/src/Api/Controllers/OrganizationDomainController.cs @@ -19,7 +19,7 @@ public class OrganizationDomainController : Controller private readonly ICreateOrganizationDomainCommand _createOrganizationDomainCommand; private readonly IVerifyOrganizationDomainCommand _verifyOrganizationDomainCommand; private readonly IDeleteOrganizationDomainCommand _deleteOrganizationDomainCommand; - private readonly IGetOrganizationDomainByIdQuery _getOrganizationDomainByIdQuery; + private readonly IGetOrganizationDomainByIdOrganizationIdQuery _getOrganizationDomainByIdAndOrganizationIdQuery; private readonly IGetOrganizationDomainByOrganizationIdQuery _getOrganizationDomainByOrganizationIdQuery; private readonly ICurrentContext _currentContext; private readonly IOrganizationRepository _organizationRepository; @@ -29,7 +29,7 @@ public class OrganizationDomainController : Controller ICreateOrganizationDomainCommand createOrganizationDomainCommand, IVerifyOrganizationDomainCommand verifyOrganizationDomainCommand, IDeleteOrganizationDomainCommand deleteOrganizationDomainCommand, - IGetOrganizationDomainByIdQuery getOrganizationDomainByIdQuery, + IGetOrganizationDomainByIdOrganizationIdQuery getOrganizationDomainByIdAndOrganizationIdQuery, IGetOrganizationDomainByOrganizationIdQuery getOrganizationDomainByOrganizationIdQuery, ICurrentContext currentContext, IOrganizationRepository organizationRepository, @@ -38,7 +38,7 @@ public class OrganizationDomainController : Controller _createOrganizationDomainCommand = createOrganizationDomainCommand; _verifyOrganizationDomainCommand = verifyOrganizationDomainCommand; _deleteOrganizationDomainCommand = deleteOrganizationDomainCommand; - _getOrganizationDomainByIdQuery = getOrganizationDomainByIdQuery; + _getOrganizationDomainByIdAndOrganizationIdQuery = getOrganizationDomainByIdAndOrganizationIdQuery; _getOrganizationDomainByOrganizationIdQuery = getOrganizationDomainByOrganizationIdQuery; _currentContext = currentContext; _organizationRepository = organizationRepository; @@ -46,71 +46,78 @@ public class OrganizationDomainController : Controller } [HttpGet("{orgId}/domain")] - public async Task> Get(string orgId) + public async Task> Get(Guid orgId) { - var orgIdGuid = new Guid(orgId); - await ValidateOrganizationAccessAsync(orgIdGuid); + await ValidateOrganizationAccessAsync(orgId); var domains = await _getOrganizationDomainByOrganizationIdQuery - .GetDomainsByOrganizationId(orgIdGuid); + .GetDomainsByOrganizationIdAsync(orgId); var response = domains.Select(x => new OrganizationDomainResponseModel(x)).ToList(); return new ListResponseModel(response); } [HttpGet("{orgId}/domain/{id}")] - public async Task Get(string orgId, string id) + public async Task Get(Guid orgId, Guid id) { - var orgIdGuid = new Guid(orgId); - var IdGuid = new Guid(id); - await ValidateOrganizationAccessAsync(orgIdGuid); + await ValidateOrganizationAccessAsync(orgId); - var domain = await _getOrganizationDomainByIdQuery.GetOrganizationDomainById(IdGuid); + var organizationDomain = await _getOrganizationDomainByIdAndOrganizationIdQuery + .GetOrganizationDomainByIdOrganizationIdAsync(id, orgId); + if (organizationDomain is null) + { + throw new NotFoundException(); + } + + return new OrganizationDomainResponseModel(organizationDomain); + } + + [HttpPost("{orgId}/domain")] + public async Task Post(Guid orgId, + [FromBody] OrganizationDomainRequestModel model) + { + await ValidateOrganizationAccessAsync(orgId); + + var organizationDomain = new OrganizationDomain + { + OrganizationId = orgId, + Txt = model.Txt, + DomainName = model.DomainName.ToLower() + }; + + organizationDomain = await _createOrganizationDomainCommand.CreateAsync(organizationDomain); + + return new OrganizationDomainResponseModel(organizationDomain); + } + + [HttpPost("{orgId}/domain/{id}/verify")] + public async Task Verify(Guid orgId, Guid id) + { + await ValidateOrganizationAccessAsync(orgId); + + var organizationDomain = await _organizationDomainRepository.GetDomainByIdOrganizationIdAsync(id, orgId); + if (organizationDomain is null) + { + throw new NotFoundException(); + } + + organizationDomain = await _verifyOrganizationDomainCommand.VerifyOrganizationDomainAsync(organizationDomain); + + return new OrganizationDomainResponseModel(organizationDomain); + } + + [HttpDelete("{orgId}/domain/{id}")] + [HttpPost("{orgId}/domain/{id}/remove")] + public async Task RemoveDomain(Guid orgId, Guid id) + { + await ValidateOrganizationAccessAsync(orgId); + + var domain = await _organizationDomainRepository.GetDomainByIdOrganizationIdAsync(id, orgId); if (domain is null) { throw new NotFoundException(); } - return new OrganizationDomainResponseModel(domain); - } - - [HttpPost("{orgId}/domain")] - public async Task Post(string orgId, - [FromBody] OrganizationDomainRequestModel model) - { - var orgIdGuid = new Guid(orgId); - await ValidateOrganizationAccessAsync(orgIdGuid); - - var organizationDomain = new OrganizationDomain - { - OrganizationId = orgIdGuid, - Txt = model.Txt, - DomainName = model.DomainName.ToLower() - }; - - var domain = await _createOrganizationDomainCommand.CreateAsync(organizationDomain); - return new OrganizationDomainResponseModel(domain); - } - - [HttpPost("{orgId}/domain/{id}/verify")] - public async Task Verify(string orgId, string id) - { - var orgIdGuid = new Guid(orgId); - var idGuid = new Guid(id); - await ValidateOrganizationAccessAsync(orgIdGuid); - - var domain = await _verifyOrganizationDomainCommand.VerifyOrganizationDomain(idGuid); - return new OrganizationDomainResponseModel(domain); - } - - [HttpDelete("{orgId}/domain/{id}")] - [HttpPost("{orgId}/domain/{id}/remove")] - public async Task RemoveDomain(string orgId, string id) - { - var orgIdGuid = new Guid(orgId); - var idGuid = new Guid(id); - await ValidateOrganizationAccessAsync(orgIdGuid); - - await _deleteOrganizationDomainCommand.DeleteAsync(idGuid); + await _deleteOrganizationDomainCommand.DeleteAsync(domain); } [AllowAnonymous] diff --git a/src/Api/Controllers/SelfHosted/SelfHostedOrganizationLicensesController.cs b/src/Api/Controllers/SelfHosted/SelfHostedOrganizationLicensesController.cs index c821204c75..783c4b71f4 100644 --- a/src/Api/Controllers/SelfHosted/SelfHostedOrganizationLicensesController.cs +++ b/src/Api/Controllers/SelfHosted/SelfHostedOrganizationLicensesController.cs @@ -1,6 +1,6 @@ -using Bit.Api.Models.Request; +using Bit.Api.AdminConsole.Models.Response.Organizations; +using Bit.Api.Models.Request; using Bit.Api.Models.Request.Organizations; -using Bit.Api.Models.Response.Organizations; using Bit.Api.Utilities; using Bit.Core.Context; using Bit.Core.Enums; diff --git a/src/Api/Models/Request/Organizations/OrganizationCreateLicenseRequestModel.cs b/src/Api/Models/Request/Organizations/OrganizationCreateLicenseRequestModel.cs index 2d9175158f..5ee7a632a6 100644 --- a/src/Api/Models/Request/Organizations/OrganizationCreateLicenseRequestModel.cs +++ b/src/Api/Models/Request/Organizations/OrganizationCreateLicenseRequestModel.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Core.Utilities; namespace Bit.Api.Models.Request.Organizations; diff --git a/src/Api/Models/Response/ProfileResponseModel.cs b/src/Api/Models/Response/ProfileResponseModel.cs index 1fbf002e27..312e7ed7e5 100644 --- a/src/Api/Models/Response/ProfileResponseModel.cs +++ b/src/Api/Models/Response/ProfileResponseModel.cs @@ -1,4 +1,5 @@ -using Bit.Api.Models.Response.Providers; +using Bit.Api.AdminConsole.Models.Response; +using Bit.Api.AdminConsole.Models.Response.Providers; using Bit.Core.Entities; using Bit.Core.Models.Api; using Bit.Core.Models.Data; diff --git a/src/Api/Models/Response/SubscriptionResponseModel.cs b/src/Api/Models/Response/SubscriptionResponseModel.cs index 553b7dd99a..883f7ac900 100644 --- a/src/Api/Models/Response/SubscriptionResponseModel.cs +++ b/src/Api/Models/Response/SubscriptionResponseModel.cs @@ -13,7 +13,6 @@ public class SubscriptionResponseModel : ResponseModel Subscription = subscription.Subscription != null ? new BillingSubscription(subscription.Subscription) : null; UpcomingInvoice = subscription.UpcomingInvoice != null ? new BillingSubscriptionUpcomingInvoice(subscription.UpcomingInvoice) : null; - Discount = subscription.Discount != null ? new BillingCustomerDiscount(subscription.Discount) : null; StorageName = user.Storage.HasValue ? CoreHelpers.ReadableBytesSize(user.Storage.Value) : null; StorageGb = user.Storage.HasValue ? Math.Round(user.Storage.Value / 1073741824D, 2) : 0; // 1 GB MaxStorageGb = user.MaxStorageGb; @@ -41,7 +40,6 @@ public class SubscriptionResponseModel : ResponseModel public short? MaxStorageGb { get; set; } public BillingSubscriptionUpcomingInvoice UpcomingInvoice { get; set; } public BillingSubscription Subscription { get; set; } - public BillingCustomerDiscount Discount { get; set; } public UserLicense License { get; set; } public DateTime? Expiration { get; set; } public bool UsingInAppPurchase { get; set; } @@ -53,10 +51,12 @@ public class BillingCustomerDiscount { Id = discount.Id; Active = discount.Active; + PercentOff = discount.PercentOff; } - public string Id { get; set; } - public bool Active { get; set; } + public string Id { get; } + public bool Active { get; } + public decimal? PercentOff { get; } } public class BillingSubscription diff --git a/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs b/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs index 3e07d505d0..c8d43293e1 100644 --- a/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs +++ b/src/Api/SecretsManager/Controllers/AccessPoliciesController.cs @@ -1,6 +1,7 @@ using Bit.Api.Models.Response; using Bit.Api.SecretsManager.Models.Request; using Bit.Api.SecretsManager.Models.Response; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Context; using Bit.Core.Enums; using Bit.Core.Exceptions; diff --git a/src/Api/SecretsManager/Controllers/SecretsManagerEventsController.cs b/src/Api/SecretsManager/Controllers/SecretsManagerEventsController.cs new file mode 100644 index 0000000000..91d350b680 --- /dev/null +++ b/src/Api/SecretsManager/Controllers/SecretsManagerEventsController.cs @@ -0,0 +1,52 @@ +using Bit.Api.Models.Response; +using Bit.Api.Utilities; +using Bit.Core.Exceptions; +using Bit.Core.Models.Data; +using Bit.Core.Repositories; +using Bit.Core.SecretsManager.AuthorizationRequirements; +using Bit.Core.SecretsManager.Repositories; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Bit.Api.SecretsManager.Controllers; + +[Authorize("secrets")] +public class SecretsManagerEventsController : Controller +{ + private readonly IAuthorizationService _authorizationService; + private readonly IEventRepository _eventRepository; + private readonly IServiceAccountRepository _serviceAccountRepository; + + public SecretsManagerEventsController( + IEventRepository eventRepository, + IServiceAccountRepository serviceAccountRepository, + IAuthorizationService authorizationService) + { + _authorizationService = authorizationService; + _serviceAccountRepository = serviceAccountRepository; + _eventRepository = eventRepository; + } + + [HttpGet("sm/events/service-accounts/{serviceAccountId}")] + public async Task> GetServiceAccountEventsAsync(Guid serviceAccountId, + [FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, + [FromQuery] string continuationToken = null) + { + var serviceAccount = await _serviceAccountRepository.GetByIdAsync(serviceAccountId); + var authorizationResult = + await _authorizationService.AuthorizeAsync(User, serviceAccount, ServiceAccountOperations.ReadEvents); + + if (!authorizationResult.Succeeded) + { + throw new NotFoundException(); + } + + var dateRange = ApiHelpers.GetDateRange(start, end); + + var result = await _eventRepository.GetManyByOrganizationServiceAccountAsync(serviceAccount.OrganizationId, + serviceAccount.Id, dateRange.Item1, dateRange.Item2, + new PageOptions { ContinuationToken = continuationToken }); + var responses = result.Data.Select(e => new EventResponseModel(e)); + return new ListResponseModel(responses, result.ContinuationToken); + } +} diff --git a/src/Api/SecretsManager/Models/Response/PotentialGranteeResponseModel.cs b/src/Api/SecretsManager/Models/Response/PotentialGranteeResponseModel.cs index e624370830..a64c0d86db 100644 --- a/src/Api/SecretsManager/Models/Response/PotentialGranteeResponseModel.cs +++ b/src/Api/SecretsManager/Models/Response/PotentialGranteeResponseModel.cs @@ -1,4 +1,4 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.Models.Api; using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.SecretsManager.Entities; diff --git a/src/Api/Utilities/ApiHelpers.cs b/src/Api/Utilities/ApiHelpers.cs index 58097089f4..f4f1830e16 100644 --- a/src/Api/Utilities/ApiHelpers.cs +++ b/src/Api/Utilities/ApiHelpers.cs @@ -1,6 +1,7 @@ using System.Text.Json; using Azure.Messaging.EventGrid; using Azure.Messaging.EventGrid.SystemEvents; +using Bit.Core.Exceptions; using Bit.Core.Utilities; using Microsoft.AspNetCore.Mvc; @@ -69,4 +70,35 @@ public static class ApiHelpers return new OkObjectResult(response); } + + /// + /// Validates and returns a date range. Currently used for fetching events. + /// + /// start date and time + /// end date and time + /// + /// If start or end are null, will return a range of the last 30 days. + /// If a time span greater than 367 days is passed will throw BadRequestException. + /// + public static Tuple GetDateRange(DateTime? start, DateTime? end) + { + if (!end.HasValue || !start.HasValue) + { + end = DateTime.UtcNow.Date.AddDays(1).AddMilliseconds(-1); + start = DateTime.UtcNow.Date.AddDays(-30); + } + else if (start.Value > end.Value) + { + var newEnd = start; + start = end; + end = newEnd; + } + + if ((end.Value - start.Value) > TimeSpan.FromDays(367)) + { + throw new BadRequestException("Range too large."); + } + + return new Tuple(start.Value, end.Value); + } } diff --git a/src/Api/Vault/Models/Response/AttachmentResponseModel.cs b/src/Api/Vault/Models/Response/AttachmentResponseModel.cs index c7e3caabb9..f3c0261e98 100644 --- a/src/Api/Vault/Models/Response/AttachmentResponseModel.cs +++ b/src/Api/Vault/Models/Response/AttachmentResponseModel.cs @@ -1,5 +1,4 @@ -using System.Text.Json.Serialization; -using Bit.Core.Models.Api; +using Bit.Core.Models.Api; using Bit.Core.Settings; using Bit.Core.Utilities; using Bit.Core.Vault.Entities; @@ -15,7 +14,7 @@ public class AttachmentResponseModel : ResponseModel Url = data.Url; FileName = data.Data.FileName; Key = data.Data.Key; - Size = data.Data.Size; + Size = data.Data.Size.ToString(); SizeName = CoreHelpers.ReadableBytesSize(data.Data.Size); } @@ -27,7 +26,7 @@ public class AttachmentResponseModel : ResponseModel Url = $"{globalSettings.Attachment.BaseUrl}/{cipher.Id}/{id}"; FileName = data.FileName; Key = data.Key; - Size = data.Size; + Size = data.Size.ToString(); SizeName = CoreHelpers.ReadableBytesSize(data.Size); } @@ -35,8 +34,7 @@ public class AttachmentResponseModel : ResponseModel public string Url { get; set; } public string FileName { get; set; } public string Key { get; set; } - [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] - public long Size { get; set; } + public string Size { get; set; } public string SizeName { get; set; } public static IEnumerable FromCipher(Cipher cipher, IGlobalSettings globalSettings) diff --git a/src/Billing/Controllers/StripeController.cs b/src/Billing/Controllers/StripeController.cs index fb63e1993e..e71e025dff 100644 --- a/src/Billing/Controllers/StripeController.cs +++ b/src/Billing/Controllers/StripeController.cs @@ -52,6 +52,7 @@ public class StripeController : Controller private readonly ICurrentContext _currentContext; private readonly GlobalSettings _globalSettings; private readonly IStripeEventService _stripeEventService; + private readonly IStripeFacade _stripeFacade; public StripeController( GlobalSettings globalSettings, @@ -70,7 +71,8 @@ public class StripeController : Controller ITaxRateRepository taxRateRepository, IUserRepository userRepository, ICurrentContext currentContext, - IStripeEventService stripeEventService) + IStripeEventService stripeEventService, + IStripeFacade stripeFacade) { _billingSettings = billingSettings?.Value; _hostingEnvironment = hostingEnvironment; @@ -97,6 +99,7 @@ public class StripeController : Controller _currentContext = currentContext; _globalSettings = globalSettings; _stripeEventService = stripeEventService; + _stripeFacade = stripeFacade; } [HttpPost("webhook")] @@ -209,48 +212,71 @@ public class StripeController : Controller else if (parsedEvent.Type.Equals(HandledStripeWebhook.UpcomingInvoice)) { var invoice = await _stripeEventService.GetInvoice(parsedEvent); - var subscriptionService = new SubscriptionService(); - var subscription = await subscriptionService.GetAsync(invoice.SubscriptionId); + + if (string.IsNullOrEmpty(invoice.SubscriptionId)) + { + _logger.LogWarning("Received 'invoice.upcoming' Event with ID '{eventId}' that did not include a Subscription ID", parsedEvent.Id); + return new OkResult(); + } + + var subscription = await _stripeFacade.GetSubscription(invoice.SubscriptionId); + if (subscription == null) { - throw new Exception("Invoice subscription is null. " + invoice.Id); + throw new Exception( + $"Received null Subscription from Stripe for ID '{invoice.SubscriptionId}' while processing Event with ID '{parsedEvent.Id}'"); } - subscription = await VerifyCorrectTaxRateForCharge(invoice, subscription); + var updatedSubscription = await VerifyCorrectTaxRateForCharge(invoice, subscription); - string email = null; - var ids = GetIdsFromMetaData(subscription.Metadata); - // org - if (ids.Item1.HasValue) + var (organizationId, userId) = GetIdsFromMetaData(updatedSubscription.Metadata); + + var invoiceLineItemDescriptions = invoice.Lines.Select(i => i.Description).ToList(); + + async Task SendEmails(IEnumerable emails) { - // sponsored org - if (IsSponsoredSubscription(subscription)) - { - await _validateSponsorshipCommand.ValidateSponsorshipAsync(ids.Item1.Value); - } + var validEmails = emails.Where(e => !string.IsNullOrEmpty(e)); - var org = await _organizationRepository.GetByIdAsync(ids.Item1.Value); - if (org != null && OrgPlanForInvoiceNotifications(org)) + if (invoice.NextPaymentAttempt.HasValue) { - email = org.BillingEmail; + await _mailService.SendInvoiceUpcoming( + validEmails, + invoice.AmountDue / 100M, + invoice.NextPaymentAttempt.Value, + invoiceLineItemDescriptions, + true); } } - // user - else if (ids.Item2.HasValue) + + if (organizationId.HasValue) { - var user = await _userService.GetUserByIdAsync(ids.Item2.Value); + if (IsSponsoredSubscription(updatedSubscription)) + { + await _validateSponsorshipCommand.ValidateSponsorshipAsync(organizationId.Value); + } + + var organization = await _organizationRepository.GetByIdAsync(organizationId.Value); + + if (organization == null || !OrgPlanForInvoiceNotifications(organization)) + { + return new OkResult(); + } + + await SendEmails(new List { organization.BillingEmail }); + + var ownerEmails = await _organizationRepository.GetOwnerEmailAddressesById(organization.Id); + + await SendEmails(ownerEmails); + } + else if (userId.HasValue) + { + var user = await _userService.GetUserByIdAsync(userId.Value); + if (user.Premium) { - email = user.Email; + await SendEmails(new List { user.Email }); } } - - if (!string.IsNullOrWhiteSpace(email) && invoice.NextPaymentAttempt.HasValue) - { - var items = invoice.Lines.Select(i => i.Description).ToList(); - await _mailService.SendInvoiceUpcomingAsync(email, invoice.AmountDue / 100M, - invoice.NextPaymentAttempt.Value, items, true); - } } else if (parsedEvent.Type.Equals(HandledStripeWebhook.ChargeSucceeded)) { diff --git a/src/Billing/Services/Implementations/StripeEventService.cs b/src/Billing/Services/Implementations/StripeEventService.cs index 076602e3d2..ce7ab311ff 100644 --- a/src/Billing/Services/Implementations/StripeEventService.cs +++ b/src/Billing/Services/Implementations/StripeEventService.cs @@ -7,13 +7,16 @@ namespace Bit.Billing.Services.Implementations; public class StripeEventService : IStripeEventService { private readonly GlobalSettings _globalSettings; + private readonly ILogger _logger; private readonly IStripeFacade _stripeFacade; public StripeEventService( GlobalSettings globalSettings, + ILogger logger, IStripeFacade stripeFacade) { _globalSettings = globalSettings; + _logger = logger; _stripeFacade = stripeFacade; } @@ -26,6 +29,12 @@ public class StripeEventService : IStripeEventService return eventCharge; } + if (string.IsNullOrEmpty(eventCharge.Id)) + { + _logger.LogWarning("Cannot retrieve up-to-date Charge for Event with ID '{eventId}' because no Charge ID was included in the Event.", stripeEvent.Id); + return eventCharge; + } + var charge = await _stripeFacade.GetCharge(eventCharge.Id, new ChargeGetOptions { Expand = expand }); if (charge == null) @@ -46,6 +55,12 @@ public class StripeEventService : IStripeEventService return eventCustomer; } + if (string.IsNullOrEmpty(eventCustomer.Id)) + { + _logger.LogWarning("Cannot retrieve up-to-date Customer for Event with ID '{eventId}' because no Customer ID was included in the Event.", stripeEvent.Id); + return eventCustomer; + } + var customer = await _stripeFacade.GetCustomer(eventCustomer.Id, new CustomerGetOptions { Expand = expand }); if (customer == null) @@ -66,6 +81,12 @@ public class StripeEventService : IStripeEventService return eventInvoice; } + if (string.IsNullOrEmpty(eventInvoice.Id)) + { + _logger.LogWarning("Cannot retrieve up-to-date Invoice for Event with ID '{eventId}' because no Invoice ID was included in the Event.", stripeEvent.Id); + return eventInvoice; + } + var invoice = await _stripeFacade.GetInvoice(eventInvoice.Id, new InvoiceGetOptions { Expand = expand }); if (invoice == null) @@ -86,6 +107,12 @@ public class StripeEventService : IStripeEventService return eventPaymentMethod; } + if (string.IsNullOrEmpty(eventPaymentMethod.Id)) + { + _logger.LogWarning("Cannot retrieve up-to-date Payment Method for Event with ID '{eventId}' because no Payment Method ID was included in the Event.", stripeEvent.Id); + return eventPaymentMethod; + } + var paymentMethod = await _stripeFacade.GetPaymentMethod(eventPaymentMethod.Id, new PaymentMethodGetOptions { Expand = expand }); if (paymentMethod == null) @@ -106,6 +133,12 @@ public class StripeEventService : IStripeEventService return eventSubscription; } + if (string.IsNullOrEmpty(eventSubscription.Id)) + { + _logger.LogWarning("Cannot retrieve up-to-date Subscription for Event with ID '{eventId}' because no Subscription ID was included in the Event.", stripeEvent.Id); + return eventSubscription; + } + var subscription = await _stripeFacade.GetSubscription(eventSubscription.Id, new SubscriptionGetOptions { Expand = expand }); if (subscription == null) @@ -132,7 +165,7 @@ public class StripeEventService : IStripeEventService (await GetCharge(stripeEvent, true, customerExpansion))?.Customer?.Metadata, HandledStripeWebhook.UpcomingInvoice => - (await GetInvoice(stripeEvent, true, customerExpansion))?.Customer?.Metadata, + await GetCustomerMetadataFromUpcomingInvoiceEvent(stripeEvent), HandledStripeWebhook.PaymentSucceeded or HandledStripeWebhook.PaymentFailed or HandledStripeWebhook.InvoiceCreated => (await GetInvoice(stripeEvent, true, customerExpansion))?.Customer?.Metadata, @@ -154,6 +187,20 @@ public class StripeEventService : IStripeEventService var customerRegion = GetCustomerRegion(customerMetadata); return customerRegion == serverRegion; + + /* In Stripe, when we receive an invoice.upcoming event, the event does not include an Invoice ID because + the invoice hasn't been created yet. Therefore, rather than retrieving the fresh Invoice with a 'customer' + expansion, we need to use the Customer ID on the event to retrieve the metadata. */ + async Task> GetCustomerMetadataFromUpcomingInvoiceEvent(Event localStripeEvent) + { + var invoice = await GetInvoice(localStripeEvent); + + var customer = !string.IsNullOrEmpty(invoice.CustomerId) + ? await _stripeFacade.GetCustomer(invoice.CustomerId) + : null; + + return customer?.Metadata; + } } private static T Extract(Event stripeEvent) diff --git a/src/Billing/Utilities/PayPalIpnClient.cs b/src/Billing/Utilities/PayPalIpnClient.cs index 15f7a2f156..1762f92a11 100644 --- a/src/Billing/Utilities/PayPalIpnClient.cs +++ b/src/Billing/Utilities/PayPalIpnClient.cs @@ -10,18 +10,22 @@ public class PayPalIpnClient { private readonly HttpClient _httpClient = new HttpClient(); private readonly Uri _ipnUri; + private readonly ILogger _logger; - public PayPalIpnClient(IOptions billingSettings) + public PayPalIpnClient(IOptions billingSettings, ILogger logger) { var bSettings = billingSettings?.Value; - _ipnUri = new Uri(bSettings.PayPal.Production ? "https://www.paypal.com/cgi-bin/webscr" : - "https://www.sandbox.paypal.com/cgi-bin/webscr"); + _ipnUri = new Uri(bSettings.PayPal.Production ? "https://ipnpb.paypal.com/cgi-bin/webscr" : + "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr"); + _logger = logger; } public async Task VerifyIpnAsync(string ipnBody) { + _logger.LogInformation("Verifying IPN with PayPal at {Timestamp}: {VerificationUri}", DateTime.UtcNow, _ipnUri); if (ipnBody == null) { + _logger.LogError("No IPN body."); throw new ArgumentException("No IPN body."); } @@ -30,6 +34,7 @@ public class PayPalIpnClient Method = HttpMethod.Post, RequestUri = _ipnUri }; + _httpClient.DefaultRequestHeaders.Add("User-Agent", "CSharp-IPN-VerificationScript"); var cmdIpnBody = string.Concat("cmd=_notify-validate&", ipnBody); request.Content = new StringContent(cmdIpnBody, Encoding.UTF8, "application/x-www-form-urlencoded"); var response = await _httpClient.SendAsync(request); @@ -42,14 +47,14 @@ public class PayPalIpnClient { return true; } - else if (responseContent.Equals("INVALID")) + + if (responseContent.Equals("INVALID")) { + _logger.LogWarning("Received an INVALID response from PayPal: {ResponseContent}", responseContent); return false; } - else - { - throw new Exception("Failed to verify IPN."); - } + _logger.LogError("Failed to verify IPN: {ResponseContent}", responseContent); + throw new Exception("Failed to verify IPN."); } public class IpnTransaction diff --git a/src/Core/Entities/Group.cs b/src/Core/AdminConsole/Entities/Group.cs similarity index 90% rename from src/Core/Entities/Group.cs rename to src/Core/AdminConsole/Entities/Group.cs index 3c15380fa4..19b9b6d005 100644 --- a/src/Core/Entities/Group.cs +++ b/src/Core/AdminConsole/Entities/Group.cs @@ -1,8 +1,9 @@ using System.ComponentModel.DataAnnotations; +using Bit.Core.Entities; using Bit.Core.Models; using Bit.Core.Utilities; -namespace Bit.Core.Entities; +namespace Bit.Core.AdminConsole.Entities; public class Group : ITableObject, IExternal { diff --git a/src/Core/Entities/GroupUser.cs b/src/Core/AdminConsole/Entities/GroupUser.cs similarity index 71% rename from src/Core/Entities/GroupUser.cs rename to src/Core/AdminConsole/Entities/GroupUser.cs index 3497c2c744..ad889c93ca 100644 --- a/src/Core/Entities/GroupUser.cs +++ b/src/Core/AdminConsole/Entities/GroupUser.cs @@ -1,4 +1,4 @@ -namespace Bit.Core.Entities; +namespace Bit.Core.AdminConsole.Entities; public class GroupUser { diff --git a/src/Core/Models/Business/ImportedGroup.cs b/src/Core/AdminConsole/Models/Business/ImportedGroup.cs similarity index 58% rename from src/Core/Models/Business/ImportedGroup.cs rename to src/Core/AdminConsole/Models/Business/ImportedGroup.cs index bd0e389339..bd4e81bf5b 100644 --- a/src/Core/Models/Business/ImportedGroup.cs +++ b/src/Core/AdminConsole/Models/Business/ImportedGroup.cs @@ -1,6 +1,6 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; -namespace Bit.Core.Models.Business; +namespace Bit.Core.AdminConsole.Models.Business; public class ImportedGroup { diff --git a/src/Core/Models/Data/GroupWithCollections.cs b/src/Core/AdminConsole/Models/Data/GroupWithCollections.cs similarity index 58% rename from src/Core/Models/Data/GroupWithCollections.cs rename to src/Core/AdminConsole/Models/Data/GroupWithCollections.cs index 3fa08bc45b..e9ba512574 100644 --- a/src/Core/Models/Data/GroupWithCollections.cs +++ b/src/Core/AdminConsole/Models/Data/GroupWithCollections.cs @@ -1,7 +1,7 @@ using System.Data; -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; -namespace Bit.Core.Models.Data; +namespace Bit.Core.AdminConsole.Models.Data; public class GroupWithCollections : Group { diff --git a/src/Core/OrganizationFeatures/Groups/CreateGroupCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommand.cs similarity index 89% rename from src/Core/OrganizationFeatures/Groups/CreateGroupCommand.cs rename to src/Core/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommand.cs index 10633a7e96..94e03afd8c 100644 --- a/src/Core/OrganizationFeatures/Groups/CreateGroupCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommand.cs @@ -1,16 +1,18 @@ -using Bit.Core.Context; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Data; -using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Tools.Enums; using Bit.Core.Tools.Models.Business; using Bit.Core.Tools.Services; -namespace Bit.Core.OrganizationFeatures.Groups; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Groups; public class CreateGroupCommand : ICreateGroupCommand { @@ -46,7 +48,7 @@ public class CreateGroupCommand : ICreateGroupCommand await GroupRepositoryUpdateUsersAsync(group, users); } - await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Created); + await _eventService.LogGroupEventAsync(group, Core.Enums.EventType.Group_Created); } public async Task CreateGroupAsync(Group group, Organization organization, EventSystemUser systemUser, @@ -61,7 +63,7 @@ public class CreateGroupCommand : ICreateGroupCommand await GroupRepositoryUpdateUsersAsync(group, users, systemUser); } - await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Created, systemUser); + await _eventService.LogGroupEventAsync(group, Core.Enums.EventType.Group_Created, systemUser); } private async Task GroupRepositoryCreateGroupAsync(Group group, Organization organization, IEnumerable collections = null) diff --git a/src/Core/OrganizationFeatures/Groups/DeleteGroupCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Groups/DeleteGroupCommand.cs similarity index 90% rename from src/Core/OrganizationFeatures/Groups/DeleteGroupCommand.cs rename to src/Core/AdminConsole/OrganizationFeatures/Groups/DeleteGroupCommand.cs index 86fb615f82..85f68eaf0a 100644 --- a/src/Core/OrganizationFeatures/Groups/DeleteGroupCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Groups/DeleteGroupCommand.cs @@ -1,11 +1,11 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.OrganizationFeatures.Groups.Interfaces; -using Bit.Core.Repositories; using Bit.Core.Services; -namespace Bit.Core.OrganizationFeatures.Groups; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Groups; public class DeleteGroupCommand : IDeleteGroupCommand { diff --git a/src/Core/OrganizationFeatures/Groups/Interfaces/ICreateGroupCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Groups/Interfaces/ICreateGroupCommand.cs similarity index 77% rename from src/Core/OrganizationFeatures/Groups/Interfaces/ICreateGroupCommand.cs rename to src/Core/AdminConsole/OrganizationFeatures/Groups/Interfaces/ICreateGroupCommand.cs index d8da08e8f2..54f5bc2ec2 100644 --- a/src/Core/OrganizationFeatures/Groups/Interfaces/ICreateGroupCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Groups/Interfaces/ICreateGroupCommand.cs @@ -1,8 +1,9 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Data; -namespace Bit.Core.OrganizationFeatures.Groups.Interfaces; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; public interface ICreateGroupCommand { diff --git a/src/Core/OrganizationFeatures/Groups/Interfaces/IDeleteGroupCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Groups/Interfaces/IDeleteGroupCommand.cs similarity index 72% rename from src/Core/OrganizationFeatures/Groups/Interfaces/IDeleteGroupCommand.cs rename to src/Core/AdminConsole/OrganizationFeatures/Groups/Interfaces/IDeleteGroupCommand.cs index 6dc9d01f99..566cdb1f24 100644 --- a/src/Core/OrganizationFeatures/Groups/Interfaces/IDeleteGroupCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Groups/Interfaces/IDeleteGroupCommand.cs @@ -1,7 +1,7 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.Enums; -namespace Bit.Core.OrganizationFeatures.Groups.Interfaces; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; public interface IDeleteGroupCommand { diff --git a/src/Core/OrganizationFeatures/Groups/Interfaces/IUpdateGroupCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Groups/Interfaces/IUpdateGroupCommand.cs similarity index 77% rename from src/Core/OrganizationFeatures/Groups/Interfaces/IUpdateGroupCommand.cs rename to src/Core/AdminConsole/OrganizationFeatures/Groups/Interfaces/IUpdateGroupCommand.cs index 7abb012fad..97b32fd950 100644 --- a/src/Core/OrganizationFeatures/Groups/Interfaces/IUpdateGroupCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Groups/Interfaces/IUpdateGroupCommand.cs @@ -1,8 +1,9 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Data; -namespace Bit.Core.OrganizationFeatures.Groups.Interfaces; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; public interface IUpdateGroupCommand { diff --git a/src/Core/OrganizationFeatures/Groups/UpdateGroupCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommand.cs similarity index 89% rename from src/Core/OrganizationFeatures/Groups/UpdateGroupCommand.cs rename to src/Core/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommand.cs index 857adcc8c0..3deb6994ac 100644 --- a/src/Core/OrganizationFeatures/Groups/UpdateGroupCommand.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommand.cs @@ -1,12 +1,14 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Data; -using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; -namespace Bit.Core.OrganizationFeatures.Groups; +namespace Bit.Core.AdminConsole.OrganizationFeatures.Groups; public class UpdateGroupCommand : IUpdateGroupCommand { @@ -36,7 +38,7 @@ public class UpdateGroupCommand : IUpdateGroupCommand await GroupRepositoryUpdateUsersAsync(group, userIds); } - await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Updated); + await _eventService.LogGroupEventAsync(group, Core.Enums.EventType.Group_Updated); } public async Task UpdateGroupAsync(Group group, Organization organization, EventSystemUser systemUser, @@ -51,7 +53,7 @@ public class UpdateGroupCommand : IUpdateGroupCommand await GroupRepositoryUpdateUsersAsync(group, userIds, systemUser); } - await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Updated, systemUser); + await _eventService.LogGroupEventAsync(group, Core.Enums.EventType.Group_Updated, systemUser); } private async Task GroupRepositoryUpdateGroupAsync(Group group, IEnumerable collections = null) diff --git a/src/Core/Repositories/IGroupRepository.cs b/src/Core/AdminConsole/Repositories/IGroupRepository.cs similarity index 90% rename from src/Core/Repositories/IGroupRepository.cs rename to src/Core/AdminConsole/Repositories/IGroupRepository.cs index f1b506de07..7e691db173 100644 --- a/src/Core/Repositories/IGroupRepository.cs +++ b/src/Core/AdminConsole/Repositories/IGroupRepository.cs @@ -1,7 +1,8 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.Models.Data; +using Bit.Core.Repositories; -namespace Bit.Core.Repositories; +namespace Bit.Core.AdminConsole.Repositories; public interface IGroupRepository : IRepository { diff --git a/src/Core/Services/IGroupService.cs b/src/Core/AdminConsole/Services/IGroupService.cs similarity index 85% rename from src/Core/Services/IGroupService.cs rename to src/Core/AdminConsole/Services/IGroupService.cs index 14967b6e85..d189a63de5 100644 --- a/src/Core/Services/IGroupService.cs +++ b/src/Core/AdminConsole/Services/IGroupService.cs @@ -1,7 +1,7 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Core.Enums; -namespace Bit.Core.Services; +namespace Bit.Core.AdminConsole.Services; public interface IGroupService { diff --git a/src/Core/Services/Implementations/GroupService.cs b/src/Core/AdminConsole/Services/Implementations/GroupService.cs similarity index 92% rename from src/Core/Services/Implementations/GroupService.cs rename to src/Core/AdminConsole/Services/Implementations/GroupService.cs index c878c321d9..62ab4ed487 100644 --- a/src/Core/Services/Implementations/GroupService.cs +++ b/src/Core/AdminConsole/Services/Implementations/GroupService.cs @@ -1,9 +1,12 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Repositories; +using Bit.Core.Services; -namespace Bit.Core.Services; +namespace Bit.Core.AdminConsole.Services.Implementations; public class GroupService : IGroupService { diff --git a/src/Core/Models/Business/SeatSubscriptionUpdate.cs b/src/Core/Models/Business/SeatSubscriptionUpdate.cs new file mode 100644 index 0000000000..8b4c613d61 --- /dev/null +++ b/src/Core/Models/Business/SeatSubscriptionUpdate.cs @@ -0,0 +1,49 @@ +using Bit.Core.Entities; +using Stripe; + +namespace Bit.Core.Models.Business; + +public class SeatSubscriptionUpdate : SubscriptionUpdate +{ + private readonly int _previousSeats; + private readonly StaticStore.Plan _plan; + private readonly long? _additionalSeats; + protected override List PlanIds => new() { _plan.PasswordManager.StripeSeatPlanId }; + public SeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats) + { + _plan = plan; + _additionalSeats = additionalSeats; + _previousSeats = organization.Seats.GetValueOrDefault(); + } + + public override List UpgradeItemsOptions(Subscription subscription) + { + var item = SubscriptionItem(subscription, PlanIds.Single()); + return new() + { + new SubscriptionItemOptions + { + Id = item?.Id, + Plan = PlanIds.Single(), + Quantity = _additionalSeats, + Deleted = (item?.Id != null && _additionalSeats == 0) ? true : (bool?)null, + } + }; + } + + public override List RevertItemsOptions(Subscription subscription) + { + + var item = SubscriptionItem(subscription, PlanIds.Single()); + return new() + { + new SubscriptionItemOptions + { + Id = item?.Id, + Plan = PlanIds.Single(), + Quantity = _previousSeats, + Deleted = _previousSeats == 0 ? true : (bool?)null, + } + }; + } +} diff --git a/src/Core/Models/Business/SecretsManagerSubscribeUpdate.cs b/src/Core/Models/Business/SecretsManagerSubscribeUpdate.cs new file mode 100644 index 0000000000..8f3fb89349 --- /dev/null +++ b/src/Core/Models/Business/SecretsManagerSubscribeUpdate.cs @@ -0,0 +1,78 @@ +using Bit.Core.Entities; +using Stripe; + +namespace Bit.Core.Models.Business; + +public class SecretsManagerSubscribeUpdate : SubscriptionUpdate +{ + private readonly StaticStore.Plan _plan; + private readonly long? _additionalSeats; + private readonly long? _additionalServiceAccounts; + private readonly int _previousSeats; + private readonly int _previousServiceAccounts; + protected override List PlanIds => new() { _plan.SecretsManager.StripeSeatPlanId, _plan.SecretsManager.StripeServiceAccountPlanId }; + public SecretsManagerSubscribeUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats, long? additionalServiceAccounts) + { + _plan = plan; + _additionalSeats = additionalSeats; + _additionalServiceAccounts = additionalServiceAccounts; + _previousSeats = organization.SmSeats.GetValueOrDefault(); + _previousServiceAccounts = organization.SmServiceAccounts.GetValueOrDefault(); + } + + public override List RevertItemsOptions(Subscription subscription) + { + var updatedItems = new List(); + + RemovePreviousSecretsManagerItems(updatedItems); + + return updatedItems; + } + + public override List UpgradeItemsOptions(Subscription subscription) + { + var updatedItems = new List(); + + AddNewSecretsManagerItems(updatedItems); + + return updatedItems; + } + + private void AddNewSecretsManagerItems(List updatedItems) + { + if (_additionalSeats > 0) + { + updatedItems.Add(new SubscriptionItemOptions + { + Price = _plan.SecretsManager.StripeSeatPlanId, + Quantity = _additionalSeats + }); + } + + if (_additionalServiceAccounts > 0) + { + updatedItems.Add(new SubscriptionItemOptions + { + Price = _plan.SecretsManager.StripeServiceAccountPlanId, + Quantity = _additionalServiceAccounts + }); + } + } + + private void RemovePreviousSecretsManagerItems(List updatedItems) + { + updatedItems.Add(new SubscriptionItemOptions + { + Price = _plan.SecretsManager.StripeSeatPlanId, + Quantity = _previousSeats, + Deleted = _previousSeats == 0 ? true : (bool?)null, + }); + + updatedItems.Add(new SubscriptionItemOptions + { + Price = _plan.SecretsManager.StripeServiceAccountPlanId, + Quantity = _previousServiceAccounts, + Deleted = _previousServiceAccounts == 0 ? true : (bool?)null, + }); + } +} diff --git a/src/Core/Models/Business/ServiceAccountSubscriptionUpdate.cs b/src/Core/Models/Business/ServiceAccountSubscriptionUpdate.cs new file mode 100644 index 0000000000..b49c9cd6c5 --- /dev/null +++ b/src/Core/Models/Business/ServiceAccountSubscriptionUpdate.cs @@ -0,0 +1,50 @@ +using Bit.Core.Entities; +using Stripe; + +namespace Bit.Core.Models.Business; + +public class ServiceAccountSubscriptionUpdate : SubscriptionUpdate +{ + private long? _prevServiceAccounts; + private readonly StaticStore.Plan _plan; + private readonly long? _additionalServiceAccounts; + protected override List PlanIds => new() { _plan.SecretsManager.StripeServiceAccountPlanId }; + + public ServiceAccountSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalServiceAccounts) + { + _plan = plan; + _additionalServiceAccounts = additionalServiceAccounts; + _prevServiceAccounts = organization.SmServiceAccounts ?? 0; + } + + public override List UpgradeItemsOptions(Subscription subscription) + { + var item = SubscriptionItem(subscription, PlanIds.Single()); + _prevServiceAccounts = item?.Quantity ?? 0; + return new() + { + new SubscriptionItemOptions + { + Id = item?.Id, + Plan = PlanIds.Single(), + Quantity = _additionalServiceAccounts, + Deleted = (item?.Id != null && _additionalServiceAccounts == 0) ? true : (bool?)null, + } + }; + } + + public override List RevertItemsOptions(Subscription subscription) + { + var item = SubscriptionItem(subscription, PlanIds.Single()); + return new() + { + new SubscriptionItemOptions + { + Id = item?.Id, + Plan = PlanIds.Single(), + Quantity = _prevServiceAccounts, + Deleted = _prevServiceAccounts == 0 ? true : (bool?)null, + } + }; + } +} diff --git a/src/Core/Models/Business/SmSeatSubscriptionUpdate.cs b/src/Core/Models/Business/SmSeatSubscriptionUpdate.cs new file mode 100644 index 0000000000..ddc126a261 --- /dev/null +++ b/src/Core/Models/Business/SmSeatSubscriptionUpdate.cs @@ -0,0 +1,50 @@ +using Bit.Core.Entities; +using Stripe; + +namespace Bit.Core.Models.Business; + +public class SmSeatSubscriptionUpdate : SubscriptionUpdate +{ + private readonly int _previousSeats; + private readonly StaticStore.Plan _plan; + private readonly long? _additionalSeats; + protected override List PlanIds => new() { _plan.SecretsManager.StripeSeatPlanId }; + + public SmSeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats) + { + _plan = plan; + _additionalSeats = additionalSeats; + _previousSeats = organization.SmSeats.GetValueOrDefault(); + } + + public override List UpgradeItemsOptions(Subscription subscription) + { + var item = SubscriptionItem(subscription, PlanIds.Single()); + return new() + { + new SubscriptionItemOptions + { + Id = item?.Id, + Plan = PlanIds.Single(), + Quantity = _additionalSeats, + Deleted = (item?.Id != null && _additionalSeats == 0) ? true : (bool?)null, + } + }; + } + + public override List RevertItemsOptions(Subscription subscription) + { + + var item = SubscriptionItem(subscription, PlanIds.Single()); + return new() + { + new SubscriptionItemOptions + { + Id = item?.Id, + Plan = PlanIds.Single(), + Quantity = _previousSeats, + Deleted = _previousSeats == 0 ? true : (bool?)null, + } + }; + } +} diff --git a/src/Core/Models/Business/SponsorOrganizationSubscriptionUpdate.cs b/src/Core/Models/Business/SponsorOrganizationSubscriptionUpdate.cs new file mode 100644 index 0000000000..88af72f199 --- /dev/null +++ b/src/Core/Models/Business/SponsorOrganizationSubscriptionUpdate.cs @@ -0,0 +1,83 @@ +using Stripe; + +namespace Bit.Core.Models.Business; + +public class SponsorOrganizationSubscriptionUpdate : SubscriptionUpdate +{ + private readonly string _existingPlanStripeId; + private readonly string _sponsoredPlanStripeId; + private readonly bool _applySponsorship; + protected override List PlanIds => new() { _existingPlanStripeId, _sponsoredPlanStripeId }; + + public SponsorOrganizationSubscriptionUpdate(StaticStore.Plan existingPlan, StaticStore.SponsoredPlan sponsoredPlan, bool applySponsorship) + { + _existingPlanStripeId = existingPlan.PasswordManager.StripePlanId; + _sponsoredPlanStripeId = sponsoredPlan?.StripePlanId; + _applySponsorship = applySponsorship; + } + + public override List RevertItemsOptions(Subscription subscription) + { + var result = new List(); + if (!string.IsNullOrWhiteSpace(AddStripePlanId)) + { + result.Add(new SubscriptionItemOptions + { + Id = AddStripeItem(subscription)?.Id, + Plan = AddStripePlanId, + Quantity = 0, + Deleted = true, + }); + } + + if (!string.IsNullOrWhiteSpace(RemoveStripePlanId)) + { + result.Add(new SubscriptionItemOptions + { + Id = RemoveStripeItem(subscription)?.Id, + Plan = RemoveStripePlanId, + Quantity = 1, + Deleted = false, + }); + } + return result; + } + + public override List UpgradeItemsOptions(Subscription subscription) + { + var result = new List(); + if (RemoveStripeItem(subscription) != null) + { + result.Add(new SubscriptionItemOptions + { + Id = RemoveStripeItem(subscription)?.Id, + Plan = RemoveStripePlanId, + Quantity = 0, + Deleted = true, + }); + } + + if (!string.IsNullOrWhiteSpace(AddStripePlanId)) + { + result.Add(new SubscriptionItemOptions + { + Id = AddStripeItem(subscription)?.Id, + Plan = AddStripePlanId, + Quantity = 1, + Deleted = false, + }); + } + return result; + } + + private string RemoveStripePlanId => _applySponsorship ? _existingPlanStripeId : _sponsoredPlanStripeId; + private string AddStripePlanId => _applySponsorship ? _sponsoredPlanStripeId : _existingPlanStripeId; + private Stripe.SubscriptionItem RemoveStripeItem(Subscription subscription) => + _applySponsorship ? + SubscriptionItem(subscription, _existingPlanStripeId) : + SubscriptionItem(subscription, _sponsoredPlanStripeId); + private Stripe.SubscriptionItem AddStripeItem(Subscription subscription) => + _applySponsorship ? + SubscriptionItem(subscription, _sponsoredPlanStripeId) : + SubscriptionItem(subscription, _existingPlanStripeId); +} diff --git a/src/Core/Models/Business/StorageSubscriptionUpdate.cs b/src/Core/Models/Business/StorageSubscriptionUpdate.cs new file mode 100644 index 0000000000..30ab2428e2 --- /dev/null +++ b/src/Core/Models/Business/StorageSubscriptionUpdate.cs @@ -0,0 +1,53 @@ +using Stripe; + +namespace Bit.Core.Models.Business; + +public class StorageSubscriptionUpdate : SubscriptionUpdate +{ + private long? _prevStorage; + private readonly string _plan; + private readonly long? _additionalStorage; + protected override List PlanIds => new() { _plan }; + + public StorageSubscriptionUpdate(string plan, long? additionalStorage) + { + _plan = plan; + _additionalStorage = additionalStorage; + } + + public override List UpgradeItemsOptions(Subscription subscription) + { + var item = SubscriptionItem(subscription, PlanIds.Single()); + _prevStorage = item?.Quantity ?? 0; + return new() + { + new SubscriptionItemOptions + { + Id = item?.Id, + Plan = _plan, + Quantity = _additionalStorage, + Deleted = (item?.Id != null && _additionalStorage == 0) ? true : (bool?)null, + } + }; + } + + public override List RevertItemsOptions(Subscription subscription) + { + if (!_prevStorage.HasValue) + { + throw new Exception("Unknown previous value, must first call UpgradeItemsOptions"); + } + + var item = SubscriptionItem(subscription, PlanIds.Single()); + return new() + { + new SubscriptionItemOptions + { + Id = item?.Id, + Plan = _plan, + Quantity = _prevStorage.Value, + Deleted = _prevStorage.Value == 0 ? true : (bool?)null, + } + }; + } +} diff --git a/src/Core/Models/Business/SubscriptionInfo.cs b/src/Core/Models/Business/SubscriptionInfo.cs index f8284e0e30..8a0a6add74 100644 --- a/src/Core/Models/Business/SubscriptionInfo.cs +++ b/src/Core/Models/Business/SubscriptionInfo.cs @@ -4,7 +4,7 @@ namespace Bit.Core.Models.Business; public class SubscriptionInfo { - public BillingCustomerDiscount Discount { get; set; } + public BillingCustomerDiscount CustomerDiscount { get; set; } public BillingSubscription Subscription { get; set; } public BillingUpcomingInvoice UpcomingInvoice { get; set; } public bool UsingInAppPurchase { get; set; } @@ -17,10 +17,12 @@ public class SubscriptionInfo { Id = discount.Id; Active = discount.Start != null && discount.End == null; + PercentOff = discount.Coupon?.PercentOff; } public string Id { get; } public bool Active { get; } + public decimal? PercentOff { get; } } public class BillingSubscription diff --git a/src/Core/Models/Business/SubscriptionUpdate.cs b/src/Core/Models/Business/SubscriptionUpdate.cs index 0dcf696dbc..497a455d6c 100644 --- a/src/Core/Models/Business/SubscriptionUpdate.cs +++ b/src/Core/Models/Business/SubscriptionUpdate.cs @@ -1,5 +1,4 @@ -using Bit.Core.Entities; -using Stripe; +using Stripe; namespace Bit.Core.Models.Business; @@ -28,321 +27,3 @@ public abstract class SubscriptionUpdate protected static SubscriptionItem SubscriptionItem(Subscription subscription, string planId) => planId == null ? null : subscription.Items?.Data?.FirstOrDefault(i => i.Plan.Id == planId); } - -public abstract class BaseSeatSubscriptionUpdate : SubscriptionUpdate -{ - private readonly int _previousSeats; - protected readonly StaticStore.Plan Plan; - private readonly long? _additionalSeats; - - protected BaseSeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats, int previousSeats) - { - Plan = plan; - _additionalSeats = additionalSeats; - _previousSeats = previousSeats; - } - - protected abstract string GetPlanId(); - - protected override List PlanIds => new() { GetPlanId() }; - - public override List UpgradeItemsOptions(Subscription subscription) - { - var item = SubscriptionItem(subscription, PlanIds.Single()); - return new() - { - new SubscriptionItemOptions - { - Id = item?.Id, - Plan = PlanIds.Single(), - Quantity = _additionalSeats, - Deleted = (item?.Id != null && _additionalSeats == 0) ? true : (bool?)null, - } - }; - } - - public override List RevertItemsOptions(Subscription subscription) - { - - var item = SubscriptionItem(subscription, PlanIds.Single()); - return new() - { - new SubscriptionItemOptions - { - Id = item?.Id, - Plan = PlanIds.Single(), - Quantity = _previousSeats, - Deleted = _previousSeats == 0 ? true : (bool?)null, - } - }; - } -} - -public class SeatSubscriptionUpdate : BaseSeatSubscriptionUpdate -{ - public SeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats) - : base(organization, plan, additionalSeats, organization.Seats.GetValueOrDefault()) - { } - - protected override string GetPlanId() => Plan.PasswordManager.StripeSeatPlanId; -} - -public class SmSeatSubscriptionUpdate : BaseSeatSubscriptionUpdate -{ - public SmSeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats) - : base(organization, plan, additionalSeats, organization.SmSeats.GetValueOrDefault()) - { } - - protected override string GetPlanId() => Plan.SecretsManager.StripeSeatPlanId; -} - -public class ServiceAccountSubscriptionUpdate : SubscriptionUpdate -{ - private long? _prevServiceAccounts; - private readonly StaticStore.Plan _plan; - private readonly long? _additionalServiceAccounts; - protected override List PlanIds => new() { _plan.SecretsManager.StripeServiceAccountPlanId }; - - public ServiceAccountSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalServiceAccounts) - { - _plan = plan; - _additionalServiceAccounts = additionalServiceAccounts; - _prevServiceAccounts = organization.SmServiceAccounts ?? 0; - } - - public override List UpgradeItemsOptions(Subscription subscription) - { - var item = SubscriptionItem(subscription, PlanIds.Single()); - _prevServiceAccounts = item?.Quantity ?? 0; - return new() - { - new SubscriptionItemOptions - { - Id = item?.Id, - Plan = PlanIds.Single(), - Quantity = _additionalServiceAccounts, - Deleted = (item?.Id != null && _additionalServiceAccounts == 0) ? true : (bool?)null, - } - }; - } - - public override List RevertItemsOptions(Subscription subscription) - { - var item = SubscriptionItem(subscription, PlanIds.Single()); - return new() - { - new SubscriptionItemOptions - { - Id = item?.Id, - Plan = PlanIds.Single(), - Quantity = _prevServiceAccounts, - Deleted = _prevServiceAccounts == 0 ? true : (bool?)null, - } - }; - } -} - -public class StorageSubscriptionUpdate : SubscriptionUpdate -{ - private long? _prevStorage; - private readonly string _plan; - private readonly long? _additionalStorage; - protected override List PlanIds => new() { _plan }; - - public StorageSubscriptionUpdate(string plan, long? additionalStorage) - { - _plan = plan; - _additionalStorage = additionalStorage; - } - - public override List UpgradeItemsOptions(Subscription subscription) - { - var item = SubscriptionItem(subscription, PlanIds.Single()); - _prevStorage = item?.Quantity ?? 0; - return new() - { - new SubscriptionItemOptions - { - Id = item?.Id, - Plan = _plan, - Quantity = _additionalStorage, - Deleted = (item?.Id != null && _additionalStorage == 0) ? true : (bool?)null, - } - }; - } - - public override List RevertItemsOptions(Subscription subscription) - { - if (!_prevStorage.HasValue) - { - throw new Exception("Unknown previous value, must first call UpgradeItemsOptions"); - } - - var item = SubscriptionItem(subscription, PlanIds.Single()); - return new() - { - new SubscriptionItemOptions - { - Id = item?.Id, - Plan = _plan, - Quantity = _prevStorage.Value, - Deleted = _prevStorage.Value == 0 ? true : (bool?)null, - } - }; - } -} - -public class SponsorOrganizationSubscriptionUpdate : SubscriptionUpdate -{ - private readonly string _existingPlanStripeId; - private readonly string _sponsoredPlanStripeId; - private readonly bool _applySponsorship; - protected override List PlanIds => new() { _existingPlanStripeId, _sponsoredPlanStripeId }; - - public SponsorOrganizationSubscriptionUpdate(StaticStore.Plan existingPlan, StaticStore.SponsoredPlan sponsoredPlan, bool applySponsorship) - { - _existingPlanStripeId = existingPlan.PasswordManager.StripePlanId; - _sponsoredPlanStripeId = sponsoredPlan?.StripePlanId; - _applySponsorship = applySponsorship; - } - - public override List RevertItemsOptions(Subscription subscription) - { - var result = new List(); - if (!string.IsNullOrWhiteSpace(AddStripePlanId)) - { - result.Add(new SubscriptionItemOptions - { - Id = AddStripeItem(subscription)?.Id, - Plan = AddStripePlanId, - Quantity = 0, - Deleted = true, - }); - } - - if (!string.IsNullOrWhiteSpace(RemoveStripePlanId)) - { - result.Add(new SubscriptionItemOptions - { - Id = RemoveStripeItem(subscription)?.Id, - Plan = RemoveStripePlanId, - Quantity = 1, - Deleted = false, - }); - } - return result; - } - - public override List UpgradeItemsOptions(Subscription subscription) - { - var result = new List(); - if (RemoveStripeItem(subscription) != null) - { - result.Add(new SubscriptionItemOptions - { - Id = RemoveStripeItem(subscription)?.Id, - Plan = RemoveStripePlanId, - Quantity = 0, - Deleted = true, - }); - } - - if (!string.IsNullOrWhiteSpace(AddStripePlanId)) - { - result.Add(new SubscriptionItemOptions - { - Id = AddStripeItem(subscription)?.Id, - Plan = AddStripePlanId, - Quantity = 1, - Deleted = false, - }); - } - return result; - } - - private string RemoveStripePlanId => _applySponsorship ? _existingPlanStripeId : _sponsoredPlanStripeId; - private string AddStripePlanId => _applySponsorship ? _sponsoredPlanStripeId : _existingPlanStripeId; - private Stripe.SubscriptionItem RemoveStripeItem(Subscription subscription) => - _applySponsorship ? - SubscriptionItem(subscription, _existingPlanStripeId) : - SubscriptionItem(subscription, _sponsoredPlanStripeId); - private Stripe.SubscriptionItem AddStripeItem(Subscription subscription) => - _applySponsorship ? - SubscriptionItem(subscription, _sponsoredPlanStripeId) : - SubscriptionItem(subscription, _existingPlanStripeId); - -} - -public class SecretsManagerSubscribeUpdate : SubscriptionUpdate -{ - private readonly StaticStore.Plan _plan; - private readonly long? _additionalSeats; - private readonly long? _additionalServiceAccounts; - private readonly int _previousSeats; - private readonly int _previousServiceAccounts; - protected override List PlanIds => new() { _plan.SecretsManager.StripeSeatPlanId, _plan.SecretsManager.StripeServiceAccountPlanId }; - public SecretsManagerSubscribeUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats, long? additionalServiceAccounts) - { - _plan = plan; - _additionalSeats = additionalSeats; - _additionalServiceAccounts = additionalServiceAccounts; - _previousSeats = organization.SmSeats.GetValueOrDefault(); - _previousServiceAccounts = organization.SmServiceAccounts.GetValueOrDefault(); - } - - public override List RevertItemsOptions(Subscription subscription) - { - var updatedItems = new List(); - - RemovePreviousSecretsManagerItems(updatedItems); - - return updatedItems; - } - - public override List UpgradeItemsOptions(Subscription subscription) - { - var updatedItems = new List(); - - AddNewSecretsManagerItems(updatedItems); - - return updatedItems; - } - - private void AddNewSecretsManagerItems(List updatedItems) - { - if (_additionalSeats > 0) - { - updatedItems.Add(new SubscriptionItemOptions - { - Price = _plan.SecretsManager.StripeSeatPlanId, - Quantity = _additionalSeats - }); - } - - if (_additionalServiceAccounts > 0) - { - updatedItems.Add(new SubscriptionItemOptions - { - Price = _plan.SecretsManager.StripeServiceAccountPlanId, - Quantity = _additionalServiceAccounts - }); - } - } - - private void RemovePreviousSecretsManagerItems(List updatedItems) - { - updatedItems.Add(new SubscriptionItemOptions - { - Price = _plan.SecretsManager.StripeSeatPlanId, - Quantity = _previousSeats, - Deleted = _previousSeats == 0 ? true : (bool?)null, - }); - - updatedItems.Add(new SubscriptionItemOptions - { - Price = _plan.SecretsManager.StripeServiceAccountPlanId, - Quantity = _previousServiceAccounts, - Deleted = _previousServiceAccounts == 0 ? true : (bool?)null, - }); - } -} diff --git a/src/Core/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommand.cs b/src/Core/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommand.cs index 26f6682ed6..1d7eb8f2ba 100644 --- a/src/Core/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommand.cs @@ -1,4 +1,5 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Data; diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/DeleteOrganizationDomainCommand.cs b/src/Core/OrganizationFeatures/OrganizationDomains/DeleteOrganizationDomainCommand.cs index c420609130..f0162f7a50 100644 --- a/src/Core/OrganizationFeatures/OrganizationDomains/DeleteOrganizationDomainCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationDomains/DeleteOrganizationDomainCommand.cs @@ -1,5 +1,5 @@ -using Bit.Core.Enums; -using Bit.Core.Exceptions; +using Bit.Core.Entities; +using Bit.Core.Enums; using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; @@ -18,15 +18,9 @@ public class DeleteOrganizationDomainCommand : IDeleteOrganizationDomainCommand _eventService = eventService; } - public async Task DeleteAsync(Guid id) + public async Task DeleteAsync(OrganizationDomain organizationDomain) { - var domain = await _organizationDomainRepository.GetByIdAsync(id); - if (domain is null) - { - throw new NotFoundException(); - } - - await _organizationDomainRepository.DeleteAsync(domain); - await _eventService.LogOrganizationDomainEventAsync(domain, EventType.OrganizationDomain_Removed); + await _organizationDomainRepository.DeleteAsync(organizationDomain); + await _eventService.LogOrganizationDomainEventAsync(organizationDomain, EventType.OrganizationDomain_Removed); } } diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdQuery.cs b/src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdOrganizationIdQuery.cs similarity index 52% rename from src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdQuery.cs rename to src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdOrganizationIdQuery.cs index 8037fa8ec2..1ed2207685 100644 --- a/src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdQuery.cs +++ b/src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdOrganizationIdQuery.cs @@ -4,15 +4,15 @@ using Bit.Core.Repositories; namespace Bit.Core.OrganizationFeatures.OrganizationDomains; -public class GetOrganizationDomainByIdQuery : IGetOrganizationDomainByIdQuery +public class GetOrganizationDomainByIdOrganizationIdQuery : IGetOrganizationDomainByIdOrganizationIdQuery { private readonly IOrganizationDomainRepository _organizationDomainRepository; - public GetOrganizationDomainByIdQuery(IOrganizationDomainRepository organizationDomainRepository) + public GetOrganizationDomainByIdOrganizationIdQuery(IOrganizationDomainRepository organizationDomainRepository) { _organizationDomainRepository = organizationDomainRepository; } - public async Task GetOrganizationDomainById(Guid id) - => await _organizationDomainRepository.GetByIdAsync(id); + public async Task GetOrganizationDomainByIdOrganizationIdAsync(Guid id, Guid organizationId) + => await _organizationDomainRepository.GetDomainByIdOrganizationIdAsync(id, organizationId); } diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByOrganizationIdQuery.cs b/src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByOrganizationIdQuery.cs index 6b94dbf173..c1fef05088 100644 --- a/src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByOrganizationIdQuery.cs +++ b/src/Core/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByOrganizationIdQuery.cs @@ -13,6 +13,6 @@ public class GetOrganizationDomainByOrganizationIdQuery : IGetOrganizationDomain _organizationDomainRepository = organizationDomainRepository; } - public async Task> GetDomainsByOrganizationId(Guid orgId) + public async Task> GetDomainsByOrganizationIdAsync(Guid orgId) => await _organizationDomainRepository.GetDomainsByOrganizationIdAsync(orgId); } diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IDeleteOrganizationDomainCommand.cs b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IDeleteOrganizationDomainCommand.cs index 4a5cc1c556..8b11bc61ed 100644 --- a/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IDeleteOrganizationDomainCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IDeleteOrganizationDomainCommand.cs @@ -1,6 +1,8 @@ -namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; +using Bit.Core.Entities; + +namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; public interface IDeleteOrganizationDomainCommand { - Task DeleteAsync(Guid id); + Task DeleteAsync(OrganizationDomain organizationDomain); } diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByIdOrganizationIdQuery.cs b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByIdOrganizationIdQuery.cs new file mode 100644 index 0000000000..12fe643a6b --- /dev/null +++ b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByIdOrganizationIdQuery.cs @@ -0,0 +1,8 @@ +using Bit.Core.Entities; + +namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; + +public interface IGetOrganizationDomainByIdOrganizationIdQuery +{ + Task GetOrganizationDomainByIdOrganizationIdAsync(Guid id, Guid organizationId); +} diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByIdQuery.cs b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByIdQuery.cs deleted file mode 100644 index 765007f42f..0000000000 --- a/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByIdQuery.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Bit.Core.Entities; - -namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; - -public interface IGetOrganizationDomainByIdQuery -{ - Task GetOrganizationDomainById(Guid id); -} diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByOrganizationIdQuery.cs b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByOrganizationIdQuery.cs index 1377cb48fb..d1fb642a53 100644 --- a/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByOrganizationIdQuery.cs +++ b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IGetOrganizationDomainByOrganizationIdQuery.cs @@ -4,5 +4,5 @@ namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; public interface IGetOrganizationDomainByOrganizationIdQuery { - Task> GetDomainsByOrganizationId(Guid orgId); + Task> GetDomainsByOrganizationIdAsync(Guid orgId); } diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IVerifyOrganizationDomainCommand.cs b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IVerifyOrganizationDomainCommand.cs index 1d070cf3c1..fe4c38aa87 100644 --- a/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IVerifyOrganizationDomainCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationDomains/Interfaces/IVerifyOrganizationDomainCommand.cs @@ -4,5 +4,5 @@ namespace Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; public interface IVerifyOrganizationDomainCommand { - Task VerifyOrganizationDomain(Guid id); + Task VerifyOrganizationDomainAsync(OrganizationDomain organizationDomain); } diff --git a/src/Core/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs b/src/Core/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs index cfa4ab1481..508a085f61 100644 --- a/src/Core/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommand.cs @@ -27,14 +27,8 @@ public class VerifyOrganizationDomainCommand : IVerifyOrganizationDomainCommand _logger = logger; } - public async Task VerifyOrganizationDomain(Guid id) + public async Task VerifyOrganizationDomainAsync(OrganizationDomain domain) { - var domain = await _organizationDomainRepository.GetByIdAsync(id); - if (domain is null) - { - throw new NotFoundException(); - } - if (domain.VerifiedDate is not null) { domain.SetLastCheckedDate(); diff --git a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs index 7add19cfd4..ed8c598abb 100644 --- a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs +++ b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs @@ -1,8 +1,8 @@ using Bit.Core.AdminConsole.OrganizationAuth; using Bit.Core.AdminConsole.OrganizationAuth.Interfaces; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; using Bit.Core.Models.Business.Tokenables; -using Bit.Core.OrganizationFeatures.Groups; -using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationApiKeys; using Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationCollections; @@ -119,7 +119,7 @@ public static class OrganizationServiceCollectionExtensions { services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); } diff --git a/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpdateSecretsManagerSubscriptionCommand.cs b/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpdateSecretsManagerSubscriptionCommand.cs index aeaac974d3..30ccbe5789 100644 --- a/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpdateSecretsManagerSubscriptionCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpdateSecretsManagerSubscriptionCommand.cs @@ -66,7 +66,7 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs { if (update.SmSeatsChanged) { - await _paymentService.AdjustSeatsAsync(update.Organization, update.Plan, update.SmSeatsExcludingBase, update.ProrationDate); + await _paymentService.AdjustSmSeatsAsync(update.Organization, update.Plan, update.SmSeatsExcludingBase, update.ProrationDate); // TODO: call ReferenceEventService - see AC-1481 } diff --git a/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs b/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs index 02b3818f12..b9e3c74da7 100644 --- a/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSubscriptions/UpgradeOrganizationPlanCommand.cs @@ -1,4 +1,5 @@ using Bit.Core.AdminConsole.Models.OrganizationConnectionConfigs; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Repositories; using Bit.Core.Context; diff --git a/src/Core/Repositories/IEventRepository.cs b/src/Core/Repositories/IEventRepository.cs index 493c8c787d..dda9b589cc 100644 --- a/src/Core/Repositories/IEventRepository.cs +++ b/src/Core/Repositories/IEventRepository.cs @@ -19,4 +19,6 @@ public interface IEventRepository PageOptions pageOptions); Task CreateAsync(IEvent e); Task CreateManyAsync(IEnumerable e); + Task> GetManyByOrganizationServiceAccountAsync(Guid organizationId, Guid serviceAccountId, + DateTime startDate, DateTime endDate, PageOptions pageOptions); } diff --git a/src/Core/Repositories/IOrganizationConnectionRepository.cs b/src/Core/Repositories/IOrganizationConnectionRepository.cs index a3bdbb0370..b480333f63 100644 --- a/src/Core/Repositories/IOrganizationConnectionRepository.cs +++ b/src/Core/Repositories/IOrganizationConnectionRepository.cs @@ -5,6 +5,7 @@ namespace Bit.Core.Repositories; public interface IOrganizationConnectionRepository : IRepository { + Task GetByIdOrganizationIdAsync(Guid id, Guid organizationId); Task> GetByOrganizationIdTypeAsync(Guid organizationId, OrganizationConnectionType type); Task> GetEnabledByOrganizationIdTypeAsync(Guid organizationId, OrganizationConnectionType type); } diff --git a/src/Core/Repositories/IOrganizationDomainRepository.cs b/src/Core/Repositories/IOrganizationDomainRepository.cs index dfe5358541..1308c41109 100644 --- a/src/Core/Repositories/IOrganizationDomainRepository.cs +++ b/src/Core/Repositories/IOrganizationDomainRepository.cs @@ -9,6 +9,7 @@ public interface IOrganizationDomainRepository : IRepository> GetDomainsByOrganizationIdAsync(Guid orgId); Task> GetManyByNextRunDateAsync(DateTime date); Task GetOrganizationDomainSsoDetailsAsync(string email); + Task GetDomainByIdOrganizationIdAsync(Guid id, Guid organizationId); Task GetDomainByOrgIdAndDomainNameAsync(Guid orgId, string domainName); Task> GetExpiredOrganizationDomainsAsync(); Task DeleteExpiredAsync(int expirationPeriod); diff --git a/src/Core/Repositories/IOrganizationRepository.cs b/src/Core/Repositories/IOrganizationRepository.cs index 14126adb0a..4ac518489b 100644 --- a/src/Core/Repositories/IOrganizationRepository.cs +++ b/src/Core/Repositories/IOrganizationRepository.cs @@ -14,4 +14,5 @@ public interface IOrganizationRepository : IRepository Task GetByLicenseKeyAsync(string licenseKey); Task GetSelfHostedOrganizationDetailsById(Guid id); Task> SearchUnassignedToProviderAsync(string name, string ownerEmail, int skip, int take); + Task> GetOwnerEmailAddressesById(Guid organizationId); } diff --git a/src/Core/Repositories/TableStorage/EventRepository.cs b/src/Core/Repositories/TableStorage/EventRepository.cs index 3822ce35dc..7044850033 100644 --- a/src/Core/Repositories/TableStorage/EventRepository.cs +++ b/src/Core/Repositories/TableStorage/EventRepository.cs @@ -61,6 +61,14 @@ public class EventRepository : IEventRepository return await GetManyAsync(partitionKey, $"CipherId={cipher.Id}__Date={{0}}", startDate, endDate, pageOptions); } + public async Task> GetManyByOrganizationServiceAccountAsync(Guid organizationId, + Guid serviceAccountId, DateTime startDate, DateTime endDate, PageOptions pageOptions) + { + + return await GetManyAsync($"OrganizationId={organizationId}", + $"ServiceAccountId={serviceAccountId}__Date={{0}}", startDate, endDate, pageOptions); + } + public async Task CreateAsync(IEvent e) { if (!(e is EventTableEntity entity)) diff --git a/src/Core/SecretsManager/AuthorizationRequirements/ServiceAccountOperationRequirement.cs b/src/Core/SecretsManager/AuthorizationRequirements/ServiceAccountOperationRequirement.cs index 23f312d0b8..df09cc7101 100644 --- a/src/Core/SecretsManager/AuthorizationRequirements/ServiceAccountOperationRequirement.cs +++ b/src/Core/SecretsManager/AuthorizationRequirements/ServiceAccountOperationRequirement.cs @@ -15,4 +15,5 @@ public static class ServiceAccountOperations public static readonly ServiceAccountOperationRequirement ReadAccessTokens = new() { Name = nameof(ReadAccessTokens) }; public static readonly ServiceAccountOperationRequirement CreateAccessToken = new() { Name = nameof(CreateAccessToken) }; public static readonly ServiceAccountOperationRequirement RevokeAccessTokens = new() { Name = nameof(RevokeAccessTokens) }; + public static readonly ServiceAccountOperationRequirement ReadEvents = new() { Name = nameof(ReadEvents) }; } diff --git a/src/Core/SecretsManager/Entities/AccessPolicy.cs b/src/Core/SecretsManager/Entities/AccessPolicy.cs index 6de856d8df..f450af5574 100644 --- a/src/Core/SecretsManager/Entities/AccessPolicy.cs +++ b/src/Core/SecretsManager/Entities/AccessPolicy.cs @@ -1,4 +1,5 @@ #nullable enable +using Bit.Core.AdminConsole.Entities; using Bit.Core.Entities; using Bit.Core.Utilities; diff --git a/src/Core/Services/IEventService.cs b/src/Core/Services/IEventService.cs index 10d8b6dbd0..91a1a0782e 100644 --- a/src/Core/Services/IEventService.cs +++ b/src/Core/Services/IEventService.cs @@ -1,4 +1,5 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Entities; using Bit.Core.Entities.Provider; using Bit.Core.Enums; using Bit.Core.SecretsManager.Entities; diff --git a/src/Core/Services/IMailService.cs b/src/Core/Services/IMailService.cs index 0e5831082f..6350a0e461 100644 --- a/src/Core/Services/IMailService.cs +++ b/src/Core/Services/IMailService.cs @@ -24,7 +24,17 @@ public interface IMailService Task SendOrganizationConfirmedEmailAsync(string organizationName, string email); Task SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(string organizationName, string email); Task SendPasswordlessSignInAsync(string returnUrl, string token, string email); - Task SendInvoiceUpcomingAsync(string email, decimal amount, DateTime dueDate, List items, + Task SendInvoiceUpcoming( + string email, + decimal amount, + DateTime dueDate, + List items, + bool mentionInvoices); + Task SendInvoiceUpcoming( + IEnumerable email, + decimal amount, + DateTime dueDate, + List items, bool mentionInvoices); Task SendPaymentFailedAsync(string email, decimal amount, bool mentionInvoices); Task SendAddedCreditAsync(string email, decimal amount); diff --git a/src/Core/Services/IOrganizationService.cs b/src/Core/Services/IOrganizationService.cs index 3c48367d53..044c4f1470 100644 --- a/src/Core/Services/IOrganizationService.cs +++ b/src/Core/Services/IOrganizationService.cs @@ -1,4 +1,5 @@ using System.Security.Claims; +using Bit.Core.AdminConsole.Models.Business; using Bit.Core.Auth.Enums; using Bit.Core.Entities; using Bit.Core.Enums; diff --git a/src/Core/Services/IPaymentService.cs b/src/Core/Services/IPaymentService.cs index 03c2e93dd2..82386a1cf2 100644 --- a/src/Core/Services/IPaymentService.cs +++ b/src/Core/Services/IPaymentService.cs @@ -18,6 +18,7 @@ public interface IPaymentService Task PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken, short additionalStorageGb, TaxInfo taxInfo); Task AdjustSeatsAsync(Organization organization, Plan plan, int additionalSeats, DateTime? prorationDate = null); + Task AdjustSmSeatsAsync(Organization organization, Plan plan, int additionalSeats, DateTime? prorationDate = null); Task AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage, string storagePlanId, DateTime? prorationDate = null); Task AdjustServiceAccountsAsync(Organization organization, Plan plan, int additionalServiceAccounts, diff --git a/src/Core/Services/Implementations/EventService.cs b/src/Core/Services/Implementations/EventService.cs index d5dec0a106..e2545ba6e5 100644 --- a/src/Core/Services/Implementations/EventService.cs +++ b/src/Core/Services/Implementations/EventService.cs @@ -1,4 +1,5 @@ -using Bit.Core.Context; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Entities.Provider; using Bit.Core.Enums; diff --git a/src/Core/Services/Implementations/HandlebarsMailService.cs b/src/Core/Services/Implementations/HandlebarsMailService.cs index 98ff7df07b..24974f7ff0 100644 --- a/src/Core/Services/Implementations/HandlebarsMailService.cs +++ b/src/Core/Services/Implementations/HandlebarsMailService.cs @@ -285,10 +285,21 @@ public class HandlebarsMailService : IMailService await _mailDeliveryService.SendEmailAsync(message); } - public async Task SendInvoiceUpcomingAsync(string email, decimal amount, DateTime dueDate, - List items, bool mentionInvoices) + public async Task SendInvoiceUpcoming( + string email, + decimal amount, + DateTime dueDate, + List items, + bool mentionInvoices) => await SendInvoiceUpcoming(new List { email }, amount, dueDate, items, mentionInvoices); + + public async Task SendInvoiceUpcoming( + IEnumerable emails, + decimal amount, + DateTime dueDate, + List items, + bool mentionInvoices) { - var message = CreateDefaultMessage("Your Subscription Will Renew Soon", email); + var message = CreateDefaultMessage("Your Subscription Will Renew Soon", emails); var model = new InvoiceUpcomingViewModel { WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash, diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index 51216d82a0..59bf9964d4 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -1,5 +1,8 @@ using System.Security.Claims; using System.Text.Json; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Models.Business; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models.Business; using Bit.Core.Auth.Repositories; @@ -427,6 +430,9 @@ public class OrganizationService : IOrganizationService await ValidateSignUpPoliciesAsync(signup.Owner.Id); } + var flexibleCollectionsIsEnabled = + _featureService.IsEnabled(FeatureFlagKeys.FlexibleCollections, _currentContext); + var organization = new Organization { // Pre-generate the org id so that we can save it with the Stripe subscription.. @@ -464,6 +470,7 @@ public class OrganizationService : IOrganizationService Status = OrganizationStatusType.Created, UsePasswordManager = true, UseSecretsManager = signup.UseSecretsManager, + LimitCollectionCreationDeletion = !flexibleCollectionsIsEnabled }; if (signup.UseSecretsManager) diff --git a/src/Core/Services/Implementations/StripePaymentService.cs b/src/Core/Services/Implementations/StripePaymentService.cs index e1ac1ef775..0f7965db77 100644 --- a/src/Core/Services/Implementations/StripePaymentService.cs +++ b/src/Core/Services/Implementations/StripePaymentService.cs @@ -863,6 +863,11 @@ public class StripePaymentService : IPaymentService return FinalizeSubscriptionChangeAsync(organization, new SeatSubscriptionUpdate(organization, plan, additionalSeats), prorationDate); } + public Task AdjustSmSeatsAsync(Organization organization, StaticStore.Plan plan, int additionalSeats, DateTime? prorationDate = null) + { + return FinalizeSubscriptionChangeAsync(organization, new SmSeatSubscriptionUpdate(organization, plan, additionalSeats), prorationDate); + } + public Task AdjustServiceAccountsAsync(Organization organization, StaticStore.Plan plan, int additionalServiceAccounts, DateTime? prorationDate = null) { return FinalizeSubscriptionChangeAsync(organization, new ServiceAccountSubscriptionUpdate(organization, plan, additionalServiceAccounts), prorationDate); @@ -1563,7 +1568,7 @@ public class StripePaymentService : IPaymentService if (customer.Discount != null) { - subscriptionInfo.Discount = new SubscriptionInfo.BillingCustomerDiscount(customer.Discount); + subscriptionInfo.CustomerDiscount = new SubscriptionInfo.BillingCustomerDiscount(customer.Discount); } if (subscriber.IsUser()) diff --git a/src/Core/Services/NoopImplementations/NoopEventService.cs b/src/Core/Services/NoopImplementations/NoopEventService.cs index 068e03e35c..5d5dba16fb 100644 --- a/src/Core/Services/NoopImplementations/NoopEventService.cs +++ b/src/Core/Services/NoopImplementations/NoopEventService.cs @@ -1,4 +1,5 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Entities; using Bit.Core.Entities.Provider; using Bit.Core.Enums; using Bit.Core.SecretsManager.Entities; diff --git a/src/Core/Services/NoopImplementations/NoopMailService.cs b/src/Core/Services/NoopImplementations/NoopMailService.cs index 97d69cfa48..089ae18f18 100644 --- a/src/Core/Services/NoopImplementations/NoopMailService.cs +++ b/src/Core/Services/NoopImplementations/NoopMailService.cs @@ -88,11 +88,19 @@ public class NoopMailService : IMailService return Task.FromResult(0); } - public Task SendInvoiceUpcomingAsync(string email, decimal amount, DateTime dueDate, - List items, bool mentionInvoices) - { - return Task.FromResult(0); - } + public Task SendInvoiceUpcoming( + string email, + decimal amount, + DateTime dueDate, + List items, + bool mentionInvoices) => Task.FromResult(0); + + public Task SendInvoiceUpcoming( + IEnumerable emails, + decimal amount, + DateTime dueDate, + List items, + bool mentionInvoices) => Task.FromResult(0); public Task SendPaymentFailedAsync(string email, decimal amount, bool mentionInvoices) { diff --git a/src/Infrastructure.Dapper/Repositories/GroupRepository.cs b/src/Infrastructure.Dapper/AdminConsole/Repositories/GroupRepository.cs similarity index 95% rename from src/Infrastructure.Dapper/Repositories/GroupRepository.cs rename to src/Infrastructure.Dapper/AdminConsole/Repositories/GroupRepository.cs index 6ed61e8cac..5230080626 100644 --- a/src/Infrastructure.Dapper/Repositories/GroupRepository.cs +++ b/src/Infrastructure.Dapper/AdminConsole/Repositories/GroupRepository.cs @@ -1,13 +1,16 @@ using System.Data; using System.Text.Json; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Models.Data; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Entities; using Bit.Core.Models.Data; -using Bit.Core.Repositories; using Bit.Core.Settings; +using Bit.Infrastructure.Dapper.Repositories; using Dapper; using Microsoft.Data.SqlClient; -namespace Bit.Infrastructure.Dapper.Repositories; +namespace Bit.Infrastructure.Dapper.AdminConsole.Repositories; public class GroupRepository : Repository, IGroupRepository { @@ -139,7 +142,7 @@ public class GroupRepository : Repository, IGroupRepository using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.ExecuteAsync( - $"[{Schema}].[Group_CreateWithCollections]", + $"[{Schema}].[Group_CreateWithCollections_V2]", objWithCollections, commandType: CommandType.StoredProcedure); } @@ -153,7 +156,7 @@ public class GroupRepository : Repository, IGroupRepository using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.ExecuteAsync( - $"[{Schema}].[Group_UpdateWithCollections]", + $"[{Schema}].[Group_UpdateWithCollections_V2]", objWithCollections, commandType: CommandType.StoredProcedure); } diff --git a/src/Infrastructure.Dapper/DapperHelpers.cs b/src/Infrastructure.Dapper/DapperHelpers.cs index c14dfb5d46..b5e4dc6e5d 100644 --- a/src/Infrastructure.Dapper/DapperHelpers.cs +++ b/src/Infrastructure.Dapper/DapperHelpers.cs @@ -32,7 +32,7 @@ public static class DapperHelpers public static DataTable ToArrayTVP(this IEnumerable values) { var table = new DataTable(); - table.SetTypeName("[dbo].[SelectionReadOnlyArray]"); + table.SetTypeName("[dbo].[CollectionAccessSelectionType]"); var idColumn = new DataColumn("Id", typeof(Guid)); table.Columns.Add(idColumn); diff --git a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs index d7082184d3..8b8c54568a 100644 --- a/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs +++ b/src/Infrastructure.Dapper/DapperServiceCollectionExtensions.cs @@ -1,8 +1,10 @@ -using Bit.Core.Auth.Repositories; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Auth.Repositories; using Bit.Core.Repositories; using Bit.Core.SecretsManager.Repositories; using Bit.Core.Tools.Repositories; using Bit.Core.Vault.Repositories; +using Bit.Infrastructure.Dapper.AdminConsole.Repositories; using Bit.Infrastructure.Dapper.Auth.Repositories; using Bit.Infrastructure.Dapper.Repositories; using Bit.Infrastructure.Dapper.SecretsManager.Repositories; diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs index 37949da464..b9b0deefb6 100644 --- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs @@ -221,7 +221,7 @@ public class CollectionRepository : Repository, ICollectionRep using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.ExecuteAsync( - $"[{Schema}].[Collection_CreateWithGroupsAndUsers]", + $"[{Schema}].[Collection_CreateWithGroupsAndUsers_V2]", objWithGroupsAndUsers, commandType: CommandType.StoredProcedure); } @@ -237,7 +237,7 @@ public class CollectionRepository : Repository, ICollectionRep using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.ExecuteAsync( - $"[{Schema}].[Collection_UpdateWithGroupsAndUsers]", + $"[{Schema}].[Collection_UpdateWithGroupsAndUsers_V2]", objWithGroupsAndUsers, commandType: CommandType.StoredProcedure); } @@ -294,7 +294,7 @@ public class CollectionRepository : Repository, ICollectionRep using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.ExecuteAsync( - $"[{Schema}].[CollectionUser_UpdateUsers]", + $"[{Schema}].[CollectionUser_UpdateUsers_V2]", new { CollectionId = id, Users = users.ToArrayTVP() }, commandType: CommandType.StoredProcedure); } diff --git a/src/Infrastructure.Dapper/Repositories/EventRepository.cs b/src/Infrastructure.Dapper/Repositories/EventRepository.cs index 1c5b805c4c..b41687daa7 100644 --- a/src/Infrastructure.Dapper/Repositories/EventRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/EventRepository.cs @@ -118,6 +118,18 @@ public class EventRepository : Repository, IEventRepository } } + public async Task> GetManyByOrganizationServiceAccountAsync(Guid organizationId, Guid serviceAccountId, + DateTime startDate, DateTime endDate, + PageOptions pageOptions) + { + return await GetManyAsync($"[{Schema}].[Event_ReadPageByOrganizationIdServiceAccountId]", + new Dictionary + { + ["@OrganizationId"] = organizationId, + ["@ServiceAccountId"] = serviceAccountId + }, startDate, endDate, pageOptions); + } + private async Task> GetManyAsync(string sprocName, IDictionary sprocParams, DateTime startDate, DateTime endDate, PageOptions pageOptions) { @@ -187,6 +199,10 @@ public class EventRepository : Repository, IEventRepository eventsTable.Columns.Add(ipAddressColumn); var dateColumn = new DataColumn(nameof(e.Date), typeof(DateTime)); eventsTable.Columns.Add(dateColumn); + var secretIdColumn = new DataColumn(nameof(e.SecretId), typeof(Guid)); + eventsTable.Columns.Add(secretIdColumn); + var serviceAccountIdColumn = new DataColumn(nameof(e.ServiceAccountId), typeof(Guid)); + eventsTable.Columns.Add(serviceAccountIdColumn); foreach (DataColumn col in eventsTable.Columns) { @@ -217,6 +233,8 @@ public class EventRepository : Repository, IEventRepository row[deviceTypeColumn] = ev.DeviceType.HasValue ? (object)ev.DeviceType.Value : DBNull.Value; row[ipAddressColumn] = ev.IpAddress != null ? (object)ev.IpAddress : DBNull.Value; row[dateColumn] = ev.Date; + row[secretIdColumn] = ev.SecretId.HasValue ? ev.SecretId.Value : DBNull.Value; + row[serviceAccountIdColumn] = ev.ServiceAccountId.HasValue ? ev.ServiceAccountId.Value : DBNull.Value; eventsTable.Rows.Add(row); } diff --git a/src/Infrastructure.Dapper/Repositories/OrganizationConnectionRepository.cs b/src/Infrastructure.Dapper/Repositories/OrganizationConnectionRepository.cs index 7ea1c72272..0168f57e32 100644 --- a/src/Infrastructure.Dapper/Repositories/OrganizationConnectionRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/OrganizationConnectionRepository.cs @@ -14,6 +14,23 @@ public class OrganizationConnectionRepository : Repository GetByIdOrganizationIdAsync(Guid id, Guid organizationId) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + $"[{Schema}].[OrganizationConnection_ReadByIdOrganizationId]", + new + { + Id = id, + OrganizationId = organizationId + }, + commandType: CommandType.StoredProcedure); + + return results.FirstOrDefault(); + } + } + public async Task> GetByOrganizationIdTypeAsync(Guid organizationId, OrganizationConnectionType type) { using (var connection = new SqlConnection(ConnectionString)) diff --git a/src/Infrastructure.Dapper/Repositories/OrganizationDomainRepository.cs b/src/Infrastructure.Dapper/Repositories/OrganizationDomainRepository.cs index c46c994a33..6202a121d4 100644 --- a/src/Infrastructure.Dapper/Repositories/OrganizationDomainRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/OrganizationDomainRepository.cs @@ -69,6 +69,20 @@ public class OrganizationDomainRepository : Repository } } + public async Task GetDomainByIdOrganizationIdAsync(Guid id, Guid orgId) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection + .QueryAsync( + $"[{Schema}].[OrganizationDomain_ReadByIdOrganizationId]", + new { Id = id, OrganizationId = orgId }, + commandType: CommandType.StoredProcedure); + + return results.SingleOrDefault(); + } + } + public async Task GetDomainByOrgIdAndDomainNameAsync(Guid orgId, string domainName) { using (var connection = new SqlConnection(ConnectionString)) diff --git a/src/Infrastructure.Dapper/Repositories/OrganizationRepository.cs b/src/Infrastructure.Dapper/Repositories/OrganizationRepository.cs index 9d8cad0f9c..9329e23790 100644 --- a/src/Infrastructure.Dapper/Repositories/OrganizationRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/OrganizationRepository.cs @@ -149,4 +149,14 @@ public class OrganizationRepository : Repository, IOrganizat return results.ToList(); } } + + public async Task> GetOwnerEmailAddressesById(Guid organizationId) + { + await using var connection = new SqlConnection(ConnectionString); + + return await connection.QueryAsync( + $"[{Schema}].[{Table}_ReadOwnerEmailAddressesById]", + new { OrganizationId = organizationId }, + commandType: CommandType.StoredProcedure); + } } diff --git a/src/Infrastructure.Dapper/Repositories/OrganizationUserRepository.cs b/src/Infrastructure.Dapper/Repositories/OrganizationUserRepository.cs index f1c94c0d3e..2a2f0e340a 100644 --- a/src/Infrastructure.Dapper/Repositories/OrganizationUserRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/OrganizationUserRepository.cs @@ -1,5 +1,6 @@ using System.Data; using System.Text.Json; +using Bit.Core.AdminConsole.Entities; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Data; @@ -325,7 +326,7 @@ public class OrganizationUserRepository : Repository, IO using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.ExecuteAsync( - $"[{Schema}].[OrganizationUser_CreateWithCollections]", + $"[{Schema}].[OrganizationUser_CreateWithCollections_V2]", objWithCollections, commandType: CommandType.StoredProcedure); } @@ -342,7 +343,7 @@ public class OrganizationUserRepository : Repository, IO using (var connection = new SqlConnection(ConnectionString)) { var results = await connection.ExecuteAsync( - $"[{Schema}].[OrganizationUser_UpdateWithCollections]", + $"[{Schema}].[OrganizationUser_UpdateWithCollections_V2]", objWithCollections, commandType: CommandType.StoredProcedure); } diff --git a/src/Infrastructure.EntityFramework/Repositories/GroupRepository.cs b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/GroupRepository.cs similarity index 86% rename from src/Infrastructure.EntityFramework/Repositories/GroupRepository.cs rename to src/Infrastructure.EntityFramework/AdminConsole/Repositories/GroupRepository.cs index 7ddbff2811..0e91bd42ef 100644 --- a/src/Infrastructure.EntityFramework/Repositories/GroupRepository.cs +++ b/src/Infrastructure.EntityFramework/AdminConsole/Repositories/GroupRepository.cs @@ -1,19 +1,21 @@ using AutoMapper; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Models.Data; -using Bit.Core.Repositories; using Bit.Infrastructure.EntityFramework.Models; +using Bit.Infrastructure.EntityFramework.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using AdminConsoleEntities = Bit.Core.AdminConsole.Entities; -namespace Bit.Infrastructure.EntityFramework.Repositories; +namespace Bit.Infrastructure.EntityFramework.AdminConsole.Repositories; -public class GroupRepository : Repository, IGroupRepository +public class GroupRepository : Repository, IGroupRepository { public GroupRepository(IServiceScopeFactory serviceScopeFactory, IMapper mapper) : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Groups) { } - public async Task CreateAsync(Core.Entities.Group obj, IEnumerable collections) + public async Task CreateAsync(AdminConsoleEntities.Group obj, IEnumerable collections) { var grp = await base.CreateAsync(obj); using (var scope = ServiceScopeFactory.CreateScope()) @@ -52,7 +54,7 @@ public class GroupRepository : Repository, IGr } } - public async Task>> GetByIdWithCollectionsAsync(Guid id) + public async Task>> GetByIdWithCollectionsAsync(Guid id) { var grp = await base.GetByIdAsync(id); using (var scope = ServiceScopeFactory.CreateScope()) @@ -69,12 +71,12 @@ public class GroupRepository : Repository, IGr HidePasswords = c.HidePasswords, Manage = c.Manage, }).ToList(); - return new Tuple>( + return new Tuple>( grp, collections); } } - public async Task> GetManyByOrganizationIdAsync(Guid organizationId) + public async Task> GetManyByOrganizationIdAsync(Guid organizationId) { using (var scope = ServiceScopeFactory.CreateScope()) { @@ -83,11 +85,11 @@ public class GroupRepository : Repository, IGr from g in dbContext.Groups where g.OrganizationId == organizationId select g).ToListAsync(); - return Mapper.Map>(data); + return Mapper.Map>(data); } } - public async Task>>> + public async Task>>> GetManyWithCollectionsByOrganizationIdAsync(Guid organizationId) { var groups = await GetManyByOrganizationIdAsync(organizationId); @@ -102,7 +104,7 @@ public class GroupRepository : Repository, IGr var collections = query.GroupBy(c => c.GroupId).ToList(); return groups.Select(group => - new Tuple>( + new Tuple>( group, collections .FirstOrDefault(c => c.Key == group.Id)? @@ -118,7 +120,7 @@ public class GroupRepository : Repository, IGr } } - public async Task> GetManyByManyIds(IEnumerable groupIds) + public async Task> GetManyByManyIds(IEnumerable groupIds) { using (var scope = ServiceScopeFactory.CreateScope()) { @@ -127,11 +129,11 @@ public class GroupRepository : Repository, IGr where groupIds.Contains(g.Id) select g; var groups = await query.ToListAsync(); - return Mapper.Map>(groups); + return Mapper.Map>(groups); } } - public async Task> GetManyGroupUsersByOrganizationIdAsync(Guid organizationId) + public async Task> GetManyGroupUsersByOrganizationIdAsync(Guid organizationId) { using (var scope = ServiceScopeFactory.CreateScope()) { @@ -143,7 +145,7 @@ public class GroupRepository : Repository, IGr where g.OrganizationId == organizationId select gu; var groupUsers = await query.ToListAsync(); - return Mapper.Map>(groupUsers); + return Mapper.Map>(groupUsers); } } @@ -175,7 +177,7 @@ public class GroupRepository : Repository, IGr } } - public async Task ReplaceAsync(Core.Entities.Group group, IEnumerable requestedCollections) + public async Task ReplaceAsync(AdminConsoleEntities.Group group, IEnumerable requestedCollections) { await base.ReplaceAsync(group); using (var scope = ServiceScopeFactory.CreateScope()) diff --git a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs index 74716e2077..9026a7abd1 100644 --- a/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs +++ b/src/Infrastructure.EntityFramework/EntityFrameworkServiceCollectionExtensions.cs @@ -1,9 +1,11 @@ -using Bit.Core.Auth.Repositories; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Auth.Repositories; using Bit.Core.Enums; using Bit.Core.Repositories; using Bit.Core.SecretsManager.Repositories; using Bit.Core.Tools.Repositories; using Bit.Core.Vault.Repositories; +using Bit.Infrastructure.EntityFramework.AdminConsole.Repositories; using Bit.Infrastructure.EntityFramework.Auth.Repositories; using Bit.Infrastructure.EntityFramework.Repositories; using Bit.Infrastructure.EntityFramework.SecretsManager.Repositories; diff --git a/src/Infrastructure.EntityFramework/Models/Group.cs b/src/Infrastructure.EntityFramework/Models/Group.cs index eaa41bed82..8df9dbd7b5 100644 --- a/src/Infrastructure.EntityFramework/Models/Group.cs +++ b/src/Infrastructure.EntityFramework/Models/Group.cs @@ -2,7 +2,7 @@ namespace Bit.Infrastructure.EntityFramework.Models; -public class Group : Core.Entities.Group +public class Group : Core.AdminConsole.Entities.Group { public virtual Organization Organization { get; set; } public virtual ICollection GroupUsers { get; set; } @@ -12,6 +12,6 @@ public class GroupMapperProfile : Profile { public GroupMapperProfile() { - CreateMap().ReverseMap(); + CreateMap().ReverseMap(); } } diff --git a/src/Infrastructure.EntityFramework/Models/GroupUser.cs b/src/Infrastructure.EntityFramework/Models/GroupUser.cs index 3f25e7d876..4499b20f8a 100644 --- a/src/Infrastructure.EntityFramework/Models/GroupUser.cs +++ b/src/Infrastructure.EntityFramework/Models/GroupUser.cs @@ -2,7 +2,7 @@ namespace Bit.Infrastructure.EntityFramework.Models; -public class GroupUser : Core.Entities.GroupUser +public class GroupUser : Core.AdminConsole.Entities.GroupUser { public virtual Group Group { get; set; } public virtual OrganizationUser OrganizationUser { get; set; } @@ -12,7 +12,7 @@ public class GroupUserMapperProfile : Profile { public GroupUserMapperProfile() { - CreateMap().ReverseMap(); + CreateMap().ReverseMap(); } } diff --git a/src/Infrastructure.EntityFramework/Repositories/EventRepository.cs b/src/Infrastructure.EntityFramework/Repositories/EventRepository.cs index a2e56b3700..3e3ebb21ae 100644 --- a/src/Infrastructure.EntityFramework/Repositories/EventRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/EventRepository.cs @@ -49,6 +49,32 @@ public class EventRepository : Repository, IEv } } + public async Task> GetManyByOrganizationServiceAccountAsync(Guid organizationId, Guid serviceAccountId, + DateTime startDate, DateTime endDate, + PageOptions pageOptions) + { + DateTime? beforeDate = null; + if (!string.IsNullOrWhiteSpace(pageOptions.ContinuationToken) && + long.TryParse(pageOptions.ContinuationToken, out var binaryDate)) + { + beforeDate = DateTime.SpecifyKind(DateTime.FromBinary(binaryDate), DateTimeKind.Utc); + } + + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var query = new EventReadPageByOrganizationIdServiceAccountIdQuery(organizationId, serviceAccountId, + startDate, endDate, beforeDate, pageOptions); + var events = await query.Run(dbContext).ToListAsync(); + + var result = new PagedResult(); + if (events.Any() && events.Count >= pageOptions.PageSize) + { + result.ContinuationToken = events.Last().Date.ToBinary().ToString(); + } + result.Data.AddRange(events); + return result; + } + public async Task> GetManyByCipherAsync(Cipher cipher, DateTime startDate, DateTime endDate, PageOptions pageOptions) { DateTime? beforeDate = null; diff --git a/src/Infrastructure.EntityFramework/Repositories/OrganizationConnectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/OrganizationConnectionRepository.cs index 298e28e029..ad8359758b 100644 --- a/src/Infrastructure.EntityFramework/Repositories/OrganizationConnectionRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/OrganizationConnectionRepository.cs @@ -15,6 +15,17 @@ public class OrganizationConnectionRepository : Repository GetByIdOrganizationIdAsync(Guid id, Guid organizationId) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var connection = await dbContext.OrganizationConnections + .FirstOrDefaultAsync(oc => oc.Id == id && oc.OrganizationId == organizationId); + return Mapper.Map(connection); + } + } + public async Task> GetByOrganizationIdTypeAsync(Guid organizationId, OrganizationConnectionType type) { using (var scope = ServiceScopeFactory.CreateScope()) diff --git a/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs b/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs index ea507d5531..11ff8e0478 100644 --- a/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/OrganizationDomainRepository.cs @@ -93,6 +93,18 @@ public class OrganizationDomainRepository : Repository GetDomainByIdOrganizationIdAsync(Guid id, Guid orgId) + { + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var domain = await dbContext.OrganizationDomains + .Where(x => x.Id == id && x.OrganizationId == orgId) + .AsNoTracking() + .FirstOrDefaultAsync(); + + return Mapper.Map(domain); + } + public async Task GetDomainByOrgIdAndDomainNameAsync(Guid orgId, string domainName) { using var scope = ServiceScopeFactory.CreateScope(); diff --git a/src/Infrastructure.EntityFramework/Repositories/OrganizationRepository.cs b/src/Infrastructure.EntityFramework/Repositories/OrganizationRepository.cs index b7ffb9978a..62f4df63e3 100644 --- a/src/Infrastructure.EntityFramework/Repositories/OrganizationRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/OrganizationRepository.cs @@ -224,4 +224,24 @@ public class OrganizationRepository : Repository> GetOwnerEmailAddressesById(Guid organizationId) + { + using var scope = ServiceScopeFactory.CreateScope(); + + var dbContext = GetDatabaseContext(scope); + + var query = + from u in dbContext.Users + join ou in dbContext.OrganizationUsers on u.Id equals ou.UserId + where + ou.OrganizationId == organizationId && + ou.Type == OrganizationUserType.Owner && + ou.Status == OrganizationUserStatusType.Confirmed + group u by u.Email + into grouped + select grouped.Key; + + return await query.ToListAsync(); + } } diff --git a/src/Infrastructure.EntityFramework/Repositories/Queries/EventReadPageByOrganizationIdServiceAccountIdQuery.cs b/src/Infrastructure.EntityFramework/Repositories/Queries/EventReadPageByOrganizationIdServiceAccountIdQuery.cs new file mode 100644 index 0000000000..01f3a1fe14 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Repositories/Queries/EventReadPageByOrganizationIdServiceAccountIdQuery.cs @@ -0,0 +1,38 @@ +using Bit.Core.Models.Data; +using Bit.Infrastructure.EntityFramework.Models; + +namespace Bit.Infrastructure.EntityFramework.Repositories.Queries; + +public class EventReadPageByOrganizationIdServiceAccountIdQuery : IQuery +{ + private readonly Guid _organizationId; + private readonly Guid _serviceAccountId; + private readonly DateTime _startDate; + private readonly DateTime _endDate; + private readonly DateTime? _beforeDate; + private readonly PageOptions _pageOptions; + + public EventReadPageByOrganizationIdServiceAccountIdQuery(Guid organizationId, Guid serviceAccountId, + DateTime startDate, DateTime endDate, DateTime? beforeDate, PageOptions pageOptions) + { + _organizationId = organizationId; + _serviceAccountId = serviceAccountId; + _startDate = startDate; + _endDate = endDate; + _beforeDate = beforeDate; + _pageOptions = pageOptions; + } + + public IQueryable Run(DatabaseContext dbContext) + { + var q = from e in dbContext.Events + where e.Date >= _startDate && + (_beforeDate != null || e.Date <= _endDate) && + (_beforeDate == null || e.Date < _beforeDate.Value) && + e.OrganizationId == _organizationId && + e.ServiceAccountId == _serviceAccountId + orderby e.Date descending + select e; + return q.Skip(0).Take(_pageOptions.PageSize); + } +} diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs index ea97af0419..20b8d31c8f 100644 --- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs +++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs @@ -3,6 +3,8 @@ using System.Reflection; using System.Security.Claims; using System.Security.Cryptography.X509Certificates; using AspNetCoreRateLimit; +using Bit.Core.AdminConsole.Services; +using Bit.Core.AdminConsole.Services.Implementations; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Identity; using Bit.Core.Auth.IdentityServer; diff --git a/src/Sql/SecretsManager/dbo/Stored Procedures/Event/Event_ReadPageByOrganizationIdServiceAccountId.sql b/src/Sql/SecretsManager/dbo/Stored Procedures/Event/Event_ReadPageByOrganizationIdServiceAccountId.sql new file mode 100644 index 0000000000..5dc950ffff --- /dev/null +++ b/src/Sql/SecretsManager/dbo/Stored Procedures/Event/Event_ReadPageByOrganizationIdServiceAccountId.sql @@ -0,0 +1,25 @@ +CREATE PROCEDURE [dbo].[Event_ReadPageByOrganizationIdServiceAccountId] + @OrganizationId UNIQUEIDENTIFIER, + @ServiceAccountId UNIQUEIDENTIFIER, + @StartDate DATETIME2(7), + @EndDate DATETIME2(7), + @BeforeDate DATETIME2(7), + @PageSize INT +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[EventView] + WHERE + [Date] >= @StartDate + AND (@BeforeDate IS NOT NULL OR [Date] <= @EndDate) + AND (@BeforeDate IS NULL OR [Date] < @BeforeDate) + AND [OrganizationId] = @OrganizationId + AND [ServiceAccountId] = @ServiceAccountId + ORDER BY [Date] DESC + OFFSET 0 ROWS + FETCH NEXT @PageSize ROWS ONLY +END diff --git a/src/Sql/dbo/Stored Procedures/CollectionUser_UpdateUsers.sql b/src/Sql/dbo/Stored Procedures/CollectionUser_UpdateUsers.sql index 9b8d3a10c0..eb59899e3d 100644 --- a/src/Sql/dbo/Stored Procedures/CollectionUser_UpdateUsers.sql +++ b/src/Sql/dbo/Stored Procedures/CollectionUser_UpdateUsers.sql @@ -19,8 +19,7 @@ BEGIN [Target] SET [Target].[ReadOnly] = [Source].[ReadOnly], - [Target].[HidePasswords] = [Source].[HidePasswords], - [Target].[Manage] = [Source].[Manage] + [Target].[HidePasswords] = [Source].[HidePasswords] FROM [dbo].[CollectionUser] [Target] INNER JOIN @@ -30,18 +29,21 @@ BEGIN AND ( [Target].[ReadOnly] != [Source].[ReadOnly] OR [Target].[HidePasswords] != [Source].[HidePasswords] - OR [Target].[Manage] != [Source].[Manage] ) - -- Insert - INSERT INTO - [dbo].[CollectionUser] + -- Insert (with column list because a value for Manage is not being provided) + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords] + ) SELECT @CollectionId, [Source].[Id], [Source].[ReadOnly], - [Source].[HidePasswords], - [Source].[Manage] + [Source].[HidePasswords] FROM @Users [Source] INNER JOIN @@ -56,7 +58,7 @@ BEGIN [CollectionId] = @CollectionId AND [OrganizationUserId] = [Source].[Id] ) - + -- Delete DELETE CU diff --git a/src/Sql/dbo/Stored Procedures/CollectionUser_UpdateUsers_V2.sql b/src/Sql/dbo/Stored Procedures/CollectionUser_UpdateUsers_V2.sql new file mode 100644 index 0000000000..c7a68b0d1f --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/CollectionUser_UpdateUsers_V2.sql @@ -0,0 +1,83 @@ +CREATE PROCEDURE [dbo].[CollectionUser_UpdateUsers_V2] + @CollectionId UNIQUEIDENTIFIER, + @Users AS [dbo].[CollectionAccessSelectionType] READONLY +AS +BEGIN + SET NOCOUNT ON + + DECLARE @OrgId UNIQUEIDENTIFIER = ( + SELECT TOP 1 + [OrganizationId] + FROM + [dbo].[Collection] + WHERE + [Id] = @CollectionId + ) + + -- Update + UPDATE + [Target] + SET + [Target].[ReadOnly] = [Source].[ReadOnly], + [Target].[HidePasswords] = [Source].[HidePasswords], + [Target].[Manage] = [Source].[Manage] + FROM + [dbo].[CollectionUser] [Target] + INNER JOIN + @Users [Source] ON [Source].[Id] = [Target].[OrganizationUserId] + WHERE + [Target].[CollectionId] = @CollectionId + AND ( + [Target].[ReadOnly] != [Source].[ReadOnly] + OR [Target].[HidePasswords] != [Source].[HidePasswords] + OR [Target].[Manage] != [Source].[Manage] + ) + + -- Insert + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + SELECT + @CollectionId, + [Source].[Id], + [Source].[ReadOnly], + [Source].[HidePasswords], + [Source].[Manage] + FROM + @Users [Source] + INNER JOIN + [dbo].[OrganizationUser] OU ON [Source].[Id] = OU.[Id] AND OU.[OrganizationId] = @OrgId + WHERE + NOT EXISTS ( + SELECT + 1 + FROM + [dbo].[CollectionUser] + WHERE + [CollectionId] = @CollectionId + AND [OrganizationUserId] = [Source].[Id] + ) + + -- Delete + DELETE + CU + FROM + [dbo].[CollectionUser] CU + WHERE + CU.[CollectionId] = @CollectionId + AND NOT EXISTS ( + SELECT + 1 + FROM + @Users + WHERE + [Id] = CU.[OrganizationUserId] + ) + + EXEC [dbo].[User_BumpAccountRevisionDateByCollectionId] @CollectionId, @OrgId +END diff --git a/src/Sql/dbo/Stored Procedures/Collection_CreateOrUpdateAccessForMany.sql b/src/Sql/dbo/Stored Procedures/Collection_CreateOrUpdateAccessForMany.sql index e7f860fa60..6a41971aa0 100644 --- a/src/Sql/dbo/Stored Procedures/Collection_CreateOrUpdateAccessForMany.sql +++ b/src/Sql/dbo/Stored Procedures/Collection_CreateOrUpdateAccessForMany.sql @@ -1,8 +1,8 @@ CREATE PROCEDURE [dbo].[Collection_CreateOrUpdateAccessForMany] @OrganizationId UNIQUEIDENTIFIER, @CollectionIds AS [dbo].[GuidIdArray] READONLY, - @Groups AS [dbo].[SelectionReadOnlyArray] READONLY, - @Users AS [dbo].[SelectionReadOnlyArray] READONLY + @Groups AS [dbo].[CollectionAccessSelectionType] READONLY, + @Users AS [dbo].[CollectionAccessSelectionType] READONLY AS BEGIN SET NOCOUNT ON @@ -41,7 +41,15 @@ BEGIN [Target].[HidePasswords] = [Source].[HidePasswords], [Target].[Manage] = [Source].[Manage] WHEN NOT MATCHED BY TARGET - THEN INSERT VALUES + THEN INSERT + ( + [CollectionId], + [GroupId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + VALUES ( [Source].[CollectionId], [Source].[GroupId], @@ -84,7 +92,15 @@ BEGIN [Target].[HidePasswords] = [Source].[HidePasswords], [Target].[Manage] = [Source].[Manage] WHEN NOT MATCHED BY TARGET - THEN INSERT VALUES + THEN INSERT + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + VALUES ( [Source].[CollectionId], [Source].[OrganizationUserId], diff --git a/src/Sql/dbo/Stored Procedures/Collection_CreateWithGroupsAndUsers.sql b/src/Sql/dbo/Stored Procedures/Collection_CreateWithGroupsAndUsers.sql index 0087e9605c..120a5e83dd 100644 --- a/src/Sql/dbo/Stored Procedures/Collection_CreateWithGroupsAndUsers.sql +++ b/src/Sql/dbo/Stored Procedures/Collection_CreateWithGroupsAndUsers.sql @@ -27,15 +27,13 @@ BEGIN [CollectionId], [GroupId], [ReadOnly], - [HidePasswords], - [Manage] + [HidePasswords] ) SELECT @Id, [Id], [ReadOnly], - [HidePasswords], - [Manage] + [HidePasswords] FROM @Groups WHERE @@ -55,19 +53,17 @@ BEGIN [CollectionId], [OrganizationUserId], [ReadOnly], - [HidePasswords], - [Manage] + [HidePasswords] ) SELECT @Id, [Id], [ReadOnly], - [HidePasswords], - [Manage] + [HidePasswords] FROM @Users WHERE [Id] IN (SELECT [Id] FROM [AvailableUsersCTE]) EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId -END +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Collection_CreateWithGroupsAndUsers_V2.sql b/src/Sql/dbo/Stored Procedures/Collection_CreateWithGroupsAndUsers_V2.sql new file mode 100644 index 0000000000..11e2cdc070 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Collection_CreateWithGroupsAndUsers_V2.sql @@ -0,0 +1,73 @@ +CREATE PROCEDURE [dbo].[Collection_CreateWithGroupsAndUsers_V2] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Name VARCHAR(MAX), + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Groups AS [dbo].[CollectionAccessSelectionType] READONLY, + @Users AS [dbo].[CollectionAccessSelectionType] READONLY +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[Collection_Create] @Id, @OrganizationId, @Name, @ExternalId, @CreationDate, @RevisionDate + + -- Groups + ;WITH [AvailableGroupsCTE] AS( + SELECT + [Id] + FROM + [dbo].[Group] + WHERE + [OrganizationId] = @OrganizationId + ) + INSERT INTO [dbo].[CollectionGroup] + ( + [CollectionId], + [GroupId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + SELECT + @Id, + [Id], + [ReadOnly], + [HidePasswords], + [Manage] + FROM + @Groups + WHERE + [Id] IN (SELECT [Id] FROM [AvailableGroupsCTE]) + + -- Users + ;WITH [AvailableUsersCTE] AS( + SELECT + [Id] + FROM + [dbo].[OrganizationUser] + WHERE + [OrganizationId] = @OrganizationId + ) + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + SELECT + @Id, + [Id], + [ReadOnly], + [HidePasswords], + [Manage] + FROM + @Users + WHERE + [Id] IN (SELECT [Id] FROM [AvailableUsersCTE]) + + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId +END diff --git a/src/Sql/dbo/Stored Procedures/Collection_UpdateWithGroupsAndUsers.sql b/src/Sql/dbo/Stored Procedures/Collection_UpdateWithGroupsAndUsers.sql index 74328a1983..42ed69e36e 100644 --- a/src/Sql/dbo/Stored Procedures/Collection_UpdateWithGroupsAndUsers.sql +++ b/src/Sql/dbo/Stored Procedures/Collection_UpdateWithGroupsAndUsers.sql @@ -24,29 +24,33 @@ BEGIN ) MERGE [dbo].[CollectionGroup] AS [Target] - USING + USING @Groups AS [Source] ON [Target].[CollectionId] = @Id AND [Target].[GroupId] = [Source].[Id] WHEN NOT MATCHED BY TARGET AND [Source].[Id] IN (SELECT [Id] FROM [AvailableGroupsCTE]) THEN - INSERT VALUES + INSERT -- With column list because a value for Manage is not being provided + ( + [CollectionId], + [GroupId], + [ReadOnly], + [HidePasswords] + ) + VALUES ( @Id, [Source].[Id], [Source].[ReadOnly], - [Source].[HidePasswords], - [Source].[Manage] + [Source].[HidePasswords] ) WHEN MATCHED AND ( [Target].[ReadOnly] != [Source].[ReadOnly] OR [Target].[HidePasswords] != [Source].[HidePasswords] - OR [Target].[Manage] != [Source].[Manage] ) THEN UPDATE SET [Target].[ReadOnly] = [Source].[ReadOnly], - [Target].[HidePasswords] = [Source].[HidePasswords], - [Target].[Manage] = [Source].[Manage] + [Target].[HidePasswords] = [Source].[HidePasswords] WHEN NOT MATCHED BY SOURCE AND [Target].[CollectionId] = @Id THEN DELETE @@ -63,29 +67,33 @@ BEGIN ) MERGE [dbo].[CollectionUser] AS [Target] - USING + USING @Users AS [Source] ON [Target].[CollectionId] = @Id AND [Target].[OrganizationUserId] = [Source].[Id] WHEN NOT MATCHED BY TARGET AND [Source].[Id] IN (SELECT [Id] FROM [AvailableGroupsCTE]) THEN - INSERT VALUES + INSERT -- With column list because a value for Manage is not being provided + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords] + ) + VALUES ( @Id, [Source].[Id], [Source].[ReadOnly], - [Source].[HidePasswords], - [Source].[Manage] + [Source].[HidePasswords] ) WHEN MATCHED AND ( [Target].[ReadOnly] != [Source].[ReadOnly] OR [Target].[HidePasswords] != [Source].[HidePasswords] - OR [Target].[Manage] != [Source].[Manage] ) THEN UPDATE SET [Target].[ReadOnly] = [Source].[ReadOnly], - [Target].[HidePasswords] = [Source].[HidePasswords], - [Target].[Manage] = [Source].[Manage] + [Target].[HidePasswords] = [Source].[HidePasswords] WHEN NOT MATCHED BY SOURCE AND [Target].[CollectionId] = @Id THEN DELETE diff --git a/src/Sql/dbo/Stored Procedures/Collection_UpdateWithGroupsAndUsers_V2.sql b/src/Sql/dbo/Stored Procedures/Collection_UpdateWithGroupsAndUsers_V2.sql new file mode 100644 index 0000000000..1f9cff8fd8 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Collection_UpdateWithGroupsAndUsers_V2.sql @@ -0,0 +1,111 @@ +CREATE PROCEDURE [dbo].[Collection_UpdateWithGroupsAndUsers_V2] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Name VARCHAR(MAX), + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Groups AS [dbo].[CollectionAccessSelectionType] READONLY, + @Users AS [dbo].[CollectionAccessSelectionType] READONLY +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[Collection_Update] @Id, @OrganizationId, @Name, @ExternalId, @CreationDate, @RevisionDate + + -- Groups + ;WITH [AvailableGroupsCTE] AS( + SELECT + Id + FROM + [dbo].[Group] + WHERE + OrganizationId = @OrganizationId + ) + MERGE + [dbo].[CollectionGroup] AS [Target] + USING + @Groups AS [Source] + ON + [Target].[CollectionId] = @Id + AND [Target].[GroupId] = [Source].[Id] + WHEN NOT MATCHED BY TARGET + AND [Source].[Id] IN (SELECT [Id] FROM [AvailableGroupsCTE]) THEN + INSERT -- Add explicit column list + ( + [CollectionId], + [GroupId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + VALUES + ( + @Id, + [Source].[Id], + [Source].[ReadOnly], + [Source].[HidePasswords], + [Source].[Manage] + ) + WHEN MATCHED AND ( + [Target].[ReadOnly] != [Source].[ReadOnly] + OR [Target].[HidePasswords] != [Source].[HidePasswords] + OR [Target].[Manage] != [Source].[Manage] + ) THEN + UPDATE SET [Target].[ReadOnly] = [Source].[ReadOnly], + [Target].[HidePasswords] = [Source].[HidePasswords], + [Target].[Manage] = [Source].[Manage] + WHEN NOT MATCHED BY SOURCE + AND [Target].[CollectionId] = @Id THEN + DELETE + ; + + -- Users + ;WITH [AvailableGroupsCTE] AS( + SELECT + Id + FROM + [dbo].[OrganizationUser] + WHERE + OrganizationId = @OrganizationId + ) + MERGE + [dbo].[CollectionUser] AS [Target] + USING + @Users AS [Source] + ON + [Target].[CollectionId] = @Id + AND [Target].[OrganizationUserId] = [Source].[Id] + WHEN NOT MATCHED BY TARGET + AND [Source].[Id] IN (SELECT [Id] FROM [AvailableGroupsCTE]) THEN + INSERT + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + VALUES + ( + @Id, + [Source].[Id], + [Source].[ReadOnly], + [Source].[HidePasswords], + [Source].[Manage] + ) + WHEN MATCHED AND ( + [Target].[ReadOnly] != [Source].[ReadOnly] + OR [Target].[HidePasswords] != [Source].[HidePasswords] + OR [Target].[Manage] != [Source].[Manage] + ) THEN + UPDATE SET [Target].[ReadOnly] = [Source].[ReadOnly], + [Target].[HidePasswords] = [Source].[HidePasswords], + [Target].[Manage] = [Source].[Manage] + WHEN NOT MATCHED BY SOURCE + AND [Target].[CollectionId] = @Id THEN + DELETE + ; + + EXEC [dbo].[User_BumpAccountRevisionDateByCollectionId] @Id, @OrganizationId +END diff --git a/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections.sql b/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections.sql index 2538675847..b41637522e 100644 --- a/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections.sql +++ b/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections.sql @@ -26,19 +26,17 @@ BEGIN [CollectionId], [GroupId], [ReadOnly], - [HidePasswords], - [Manage] + [HidePasswords] ) SELECT [Id], @Id, [ReadOnly], - [HidePasswords], - [Manage] + [HidePasswords] FROM @Collections WHERE [Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId -END +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections_V2.sql b/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections_V2.sql new file mode 100644 index 0000000000..66c98996f5 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections_V2.sql @@ -0,0 +1,44 @@ +CREATE PROCEDURE [dbo].[Group_CreateWithCollections_V2] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Name NVARCHAR(100), + @AccessAll BIT, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Collections AS [dbo].[CollectionAccessSelectionType] READONLY +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[Group_Create] @Id, @OrganizationId, @Name, @AccessAll, @ExternalId, @CreationDate, @RevisionDate + + ;WITH [AvailableCollectionsCTE] AS( + SELECT + [Id] + FROM + [dbo].[Collection] + WHERE + [OrganizationId] = @OrganizationId + ) + INSERT INTO [dbo].[CollectionGroup] + ( + [CollectionId], + [GroupId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + SELECT + [Id], + @Id, + [ReadOnly], + [HidePasswords], + [Manage] + FROM + @Collections + WHERE + [Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) + + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId +END diff --git a/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections.sql b/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections.sql index 997476c685..86ec4342cf 100644 --- a/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections.sql +++ b/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections.sql @@ -23,29 +23,33 @@ BEGIN ) MERGE [dbo].[CollectionGroup] AS [Target] - USING + USING @Collections AS [Source] ON [Target].[CollectionId] = [Source].[Id] AND [Target].[GroupId] = @Id WHEN NOT MATCHED BY TARGET AND [Source].[Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN - INSERT VALUES + INSERT -- With column list because a value for Manage is not being provided + ( + [CollectionId], + [GroupId], + [ReadOnly], + [HidePasswords] + ) + VALUES ( [Source].[Id], @Id, [Source].[ReadOnly], - [Source].[HidePasswords], - [Source].[Manage] + [Source].[HidePasswords] ) WHEN MATCHED AND ( [Target].[ReadOnly] != [Source].[ReadOnly] OR [Target].[HidePasswords] != [Source].[HidePasswords] - OR [Target].[Manage] != [Source].[Manage] ) THEN UPDATE SET [Target].[ReadOnly] = [Source].[ReadOnly], - [Target].[HidePasswords] = [Source].[HidePasswords], - [Target].[Manage] = [Source].[Manage] + [Target].[HidePasswords] = [Source].[HidePasswords] WHEN NOT MATCHED BY SOURCE AND [Target].[GroupId] = @Id THEN DELETE diff --git a/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections_V2.sql b/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections_V2.sql new file mode 100644 index 0000000000..40f22a9687 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections_V2.sql @@ -0,0 +1,63 @@ +CREATE PROCEDURE [dbo].[Group_UpdateWithCollections_V2] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Name NVARCHAR(100), + @AccessAll BIT, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Collections AS [dbo].[CollectionAccessSelectionType] READONLY +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[Group_Update] @Id, @OrganizationId, @Name, @AccessAll, @ExternalId, @CreationDate, @RevisionDate + + ;WITH [AvailableCollectionsCTE] AS( + SELECT + Id + FROM + [dbo].[Collection] + WHERE + OrganizationId = @OrganizationId + ) + MERGE + [dbo].[CollectionGroup] AS [Target] + USING + @Collections AS [Source] + ON + [Target].[CollectionId] = [Source].[Id] + AND [Target].[GroupId] = @Id + WHEN NOT MATCHED BY TARGET + AND [Source].[Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN + INSERT + ( + [CollectionId], + [GroupId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + VALUES + ( + [Source].[Id], + @Id, + [Source].[ReadOnly], + [Source].[HidePasswords], + [Source].[Manage] + ) + WHEN MATCHED AND ( + [Target].[ReadOnly] != [Source].[ReadOnly] + OR [Target].[HidePasswords] != [Source].[HidePasswords] + OR [Target].[Manage] != [Source].[Manage] + ) THEN + UPDATE SET [Target].[ReadOnly] = [Source].[ReadOnly], + [Target].[HidePasswords] = [Source].[HidePasswords], + [Target].[Manage] = [Source].[Manage] + WHEN NOT MATCHED BY SOURCE + AND [Target].[GroupId] = @Id THEN + DELETE + ; + + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId +END diff --git a/src/Sql/dbo/Stored Procedures/OrganizationConnection_ReadByIdOrganizationId.sql b/src/Sql/dbo/Stored Procedures/OrganizationConnection_ReadByIdOrganizationId.sql new file mode 100644 index 0000000000..42c41f7e11 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationConnection_ReadByIdOrganizationId.sql @@ -0,0 +1,15 @@ +CREATE PROCEDURE [dbo].[OrganizationConnection_ReadByIdOrganizationId] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[OrganizationConnectionView] + WHERE + [Id] = @Id AND + [OrganizationId] = @OrganizationId +END diff --git a/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadByIdOrganizationId.sql b/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadByIdOrganizationId.sql new file mode 100644 index 0000000000..7777fe6831 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationDomain_ReadByIdOrganizationId.sql @@ -0,0 +1,16 @@ +CREATE PROCEDURE [dbo].[OrganizationDomain_ReadByIdOrganizationId] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + +SELECT + * +FROM + [dbo].[OrganizationDomain] +WHERE + [Id] = @Id + AND + [OrganizationId] = @OrganizationId +END diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateMany.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateMany.sql deleted file mode 100644 index 917553c34e..0000000000 --- a/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateMany.sql +++ /dev/null @@ -1,40 +0,0 @@ -CREATE PROCEDURE [dbo].[OrganizationUser_CreateMany] - @OrganizationUsersInput [dbo].[OrganizationUserType] READONLY -AS -BEGIN - SET NOCOUNT ON - - INSERT INTO [dbo].[OrganizationUser] - ( - [Id], - [OrganizationId], - [UserId], - [Email], - [Key], - [Status], - [Type], - [AccessAll], - [ExternalId], - [CreationDate], - [RevisionDate], - [Permissions], - [ResetPasswordKey] - ) - SELECT - OU.[Id], - OU.[OrganizationId], - OU.[UserId], - OU.[Email], - OU.[Key], - OU.[Status], - OU.[Type], - OU.[AccessAll], - OU.[ExternalId], - OU.[CreationDate], - OU.[RevisionDate], - OU.[Permissions], - OU.[ResetPasswordKey] - FROM - @OrganizationUsersInput OU -END -GO diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateWithCollections.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateWithCollections.sql index e1a67d645d..98809a0ec2 100644 --- a/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateWithCollections.sql +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateWithCollections.sql @@ -33,15 +33,13 @@ BEGIN [CollectionId], [OrganizationUserId], [ReadOnly], - [HidePasswords], - [Manage] + [HidePasswords] ) SELECT [Id], @Id, [ReadOnly], - [HidePasswords], - [Manage] + [HidePasswords] FROM @Collections WHERE diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateWithCollections_V2.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateWithCollections_V2.sql new file mode 100644 index 0000000000..50b1fb5fc5 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_CreateWithCollections_V2.sql @@ -0,0 +1,49 @@ +CREATE PROCEDURE [dbo].[OrganizationUser_CreateWithCollections_V2] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @Email NVARCHAR(256), + @Key VARCHAR(MAX), + @Status SMALLINT, + @Type TINYINT, + @AccessAll BIT, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Permissions NVARCHAR(MAX), + @ResetPasswordKey VARCHAR(MAX), + @Collections AS [dbo].[CollectionAccessSelectionType] READONLY, + @AccessSecretsManager BIT = 0 +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[OrganizationUser_Create] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate, @Permissions, @ResetPasswordKey, @AccessSecretsManager + + ;WITH [AvailableCollectionsCTE] AS( + SELECT + [Id] + FROM + [dbo].[Collection] + WHERE + [OrganizationId] = @OrganizationId + ) + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + SELECT + [Id], + @Id, + [ReadOnly], + [HidePasswords], + [Manage] + FROM + @Collections + WHERE + [Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) +END diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateMany.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateMany.sql deleted file mode 100644 index aad6ddedc7..0000000000 --- a/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateMany.sql +++ /dev/null @@ -1,33 +0,0 @@ -CREATE PROCEDURE [dbo].[OrganizationUser_UpdateMany] - @OrganizationUsersInput [dbo].[OrganizationUserType] READONLY -AS -BEGIN - SET NOCOUNT ON - - UPDATE - OU - SET - [OrganizationId] = OUI.[OrganizationId], - [UserId] = OUI.[UserId], - [Email] = OUI.[Email], - [Key] = OUI.[Key], - [Status] = OUI.[Status], - [Type] = OUI.[Type], - [AccessAll] = OUI.[AccessAll], - [ExternalId] = OUI.[ExternalId], - [CreationDate] = OUI.[CreationDate], - [RevisionDate] = OUI.[RevisionDate], - [Permissions] = OUI.[Permissions], - [ResetPasswordKey] = OUI.[ResetPasswordKey] - FROM - [dbo].[OrganizationUser] OU - INNER JOIN - @OrganizationUsersInput OUI ON OU.Id = OUI.Id - - EXEC [dbo].[User_BumpManyAccountRevisionDates] - ( - SELECT UserId - FROM @OrganizationUsersInput - ) -END -GO diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateWithCollections.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateWithCollections.sql index 96272867db..0a9ff4f034 100644 --- a/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateWithCollections.sql +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateWithCollections.sql @@ -24,8 +24,7 @@ BEGIN [Target] SET [Target].[ReadOnly] = [Source].[ReadOnly], - [Target].[HidePasswords] = [Source].[HidePasswords], - [Target].[Manage] = [Source].[Manage] + [Target].[HidePasswords] = [Source].[HidePasswords] FROM [dbo].[CollectionUser] AS [Target] INNER JOIN @@ -35,18 +34,21 @@ BEGIN AND ( [Target].[ReadOnly] != [Source].[ReadOnly] OR [Target].[HidePasswords] != [Source].[HidePasswords] - OR [Target].[Manage] != [Source].[Manage] ) - -- Insert - INSERT INTO - [dbo].[CollectionUser] + -- Insert (with column list because a value for Manage is not being provided) + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords] + ) SELECT [Source].[Id], @Id, [Source].[ReadOnly], - [Source].[HidePasswords], - [Source].[Manage] + [Source].[HidePasswords] FROM @Collections AS [Source] INNER JOIN diff --git a/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateWithCollections_V2.sql b/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateWithCollections_V2.sql new file mode 100644 index 0000000000..f152df3b1e --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/OrganizationUser_UpdateWithCollections_V2.sql @@ -0,0 +1,86 @@ +CREATE PROCEDURE [dbo].[OrganizationUser_UpdateWithCollections_V2] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @Email NVARCHAR(256), + @Key VARCHAR(MAX), + @Status SMALLINT, + @Type TINYINT, + @AccessAll BIT, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Permissions NVARCHAR(MAX), + @ResetPasswordKey VARCHAR(MAX), + @Collections AS [dbo].[CollectionAccessSelectionType] READONLY, + @AccessSecretsManager BIT = 0 +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[OrganizationUser_Update] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate, @Permissions, @ResetPasswordKey, @AccessSecretsManager + -- Update + UPDATE + [Target] + SET + [Target].[ReadOnly] = [Source].[ReadOnly], + [Target].[HidePasswords] = [Source].[HidePasswords], + [Target].[Manage] = [Source].[Manage] + FROM + [dbo].[CollectionUser] AS [Target] + INNER JOIN + @Collections AS [Source] ON [Source].[Id] = [Target].[CollectionId] + WHERE + [Target].[OrganizationUserId] = @Id + AND ( + [Target].[ReadOnly] != [Source].[ReadOnly] + OR [Target].[HidePasswords] != [Source].[HidePasswords] + OR [Target].[Manage] != [Source].[Manage] + ) + + -- Insert + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + SELECT + [Source].[Id], + @Id, + [Source].[ReadOnly], + [Source].[HidePasswords], + [Source].[Manage] + FROM + @Collections AS [Source] + INNER JOIN + [dbo].[Collection] C ON C.[Id] = [Source].[Id] AND C.[OrganizationId] = @OrganizationId + WHERE + NOT EXISTS ( + SELECT + 1 + FROM + [dbo].[CollectionUser] + WHERE + [CollectionId] = [Source].[Id] + AND [OrganizationUserId] = @Id + ) + + -- Delete + DELETE + CU + FROM + [dbo].[CollectionUser] CU + WHERE + CU.[OrganizationUserId] = @Id + AND NOT EXISTS ( + SELECT + 1 + FROM + @Collections + WHERE + [Id] = CU.[CollectionId] + ) +END diff --git a/src/Sql/dbo/Stored Procedures/Organization_Create.sql b/src/Sql/dbo/Stored Procedures/Organization_Create.sql index 856a58d3e8..1291e8c0a1 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_Create.sql @@ -51,7 +51,7 @@ @MaxAutoscaleSmSeats INT= null, @MaxAutoscaleSmServiceAccounts INT = null, @SecretsManagerBeta BIT = 0, - @LimitCollectionCreationDeletion BIT = 0 + @LimitCollectionCreationDeletion BIT = 1 AS BEGIN SET NOCOUNT ON diff --git a/src/Sql/dbo/Stored Procedures/User_Create.sql b/src/Sql/dbo/Stored Procedures/User_Create.sql index 65c5d21908..3aabab8c23 100644 --- a/src/Sql/dbo/Stored Procedures/User_Create.sql +++ b/src/Sql/dbo/Stored Procedures/User_Create.sql @@ -36,7 +36,6 @@ @UsesKeyConnector BIT = 0, @FailedLoginCount INT = 0, @LastFailedLoginDate DATETIME2(7), - @UnknownDeviceVerificationEnabled BIT = 1, @AvatarColor VARCHAR(7) = NULL, @LastPasswordChangeDate DATETIME2(7) = NULL, @LastKdfChangeDate DATETIME2(7) = NULL, @@ -83,7 +82,6 @@ BEGIN [UsesKeyConnector], [FailedLoginCount], [LastFailedLoginDate], - [UnknownDeviceVerificationEnabled], [AvatarColor], [KdfMemory], [KdfParallelism], @@ -129,7 +127,6 @@ BEGIN @UsesKeyConnector, @FailedLoginCount, @LastFailedLoginDate, - @UnknownDeviceVerificationEnabled, @AvatarColor, @KdfMemory, @KdfParallelism, diff --git a/src/Sql/dbo/Stored Procedures/User_Update.sql b/src/Sql/dbo/Stored Procedures/User_Update.sql index 60a5119366..5725f243ff 100644 --- a/src/Sql/dbo/Stored Procedures/User_Update.sql +++ b/src/Sql/dbo/Stored Procedures/User_Update.sql @@ -36,7 +36,6 @@ @UsesKeyConnector BIT = 0, @FailedLoginCount INT, @LastFailedLoginDate DATETIME2(7), - @UnknownDeviceVerificationEnabled BIT = 1, @AvatarColor VARCHAR(7), @LastPasswordChangeDate DATETIME2(7) = NULL, @LastKdfChangeDate DATETIME2(7) = NULL, @@ -85,7 +84,6 @@ BEGIN [UsesKeyConnector] = @UsesKeyConnector, [FailedLoginCount] = @FailedLoginCount, [LastFailedLoginDate] = @LastFailedLoginDate, - [UnknownDeviceVerificationEnabled] = @UnknownDeviceVerificationEnabled, [AvatarColor] = @AvatarColor, [LastPasswordChangeDate] = @LastPasswordChangeDate, [LastKdfChangeDate] = @LastKdfChangeDate, diff --git a/src/Sql/dbo/Tables/User.sql b/src/Sql/dbo/Tables/User.sql index 62ff0edf2b..0c34784e97 100644 --- a/src/Sql/dbo/Tables/User.sql +++ b/src/Sql/dbo/Tables/User.sql @@ -36,8 +36,7 @@ [UsesKeyConnector] BIT NOT NULL, [FailedLoginCount] INT CONSTRAINT [D_User_FailedLoginCount] DEFAULT ((0)) NOT NULL, [LastFailedLoginDate] DATETIME2 (7) NULL, - [UnknownDeviceVerificationEnabled] BIT CONSTRAINT [D_User_UnknownDeviceVerificationEnabled] DEFAULT ((1)) NOT NULL, - [AvatarColor] VARCHAR (7) NULL, + [AvatarColor] VARCHAR(7) NULL, [LastPasswordChangeDate] DATETIME2 (7) NULL, [LastKdfChangeDate] DATETIME2 (7) NULL, [LastKeyRotationDate] DATETIME2 (7) NULL, diff --git a/src/Sql/dbo/User Defined Types/CollectionAccessSelection.sql b/src/Sql/dbo/User Defined Types/CollectionAccessSelection.sql new file mode 100644 index 0000000000..07bb178464 --- /dev/null +++ b/src/Sql/dbo/User Defined Types/CollectionAccessSelection.sql @@ -0,0 +1,6 @@ +CREATE TYPE [dbo].[CollectionAccessSelectionType] AS TABLE ( + [Id] UNIQUEIDENTIFIER NOT NULL, + [ReadOnly] BIT NOT NULL, + [HidePasswords] BIT NOT NULL, + [Manage] BIT NOT NULL); + diff --git a/src/Sql/dbo/User Defined Types/OrganizationUserType.sql b/src/Sql/dbo/User Defined Types/OrganizationUserType.sql deleted file mode 100644 index 6d18cf0441..0000000000 --- a/src/Sql/dbo/User Defined Types/OrganizationUserType.sql +++ /dev/null @@ -1,15 +0,0 @@ -CREATE TYPE [dbo].[OrganizationUserType] AS TABLE( - [Id] UNIQUEIDENTIFIER, - [OrganizationId] UNIQUEIDENTIFIER, - [UserId] UNIQUEIDENTIFIER, - [Email] NVARCHAR(256), - [Key] VARCHAR(MAX), - [Status] SMALLINT, - [Type] TINYINT, - [AccessAll] BIT, - [ExternalId] NVARCHAR(300), - [CreationDate] DATETIME2(7), - [RevisionDate] DATETIME2(7), - [Permissions] NVARCHAR(MAX), - [ResetPasswordKey] VARCHAR(MAX) -) diff --git a/src/Sql/dbo/User Defined Types/SelectionReadOnlyArray.sql b/src/Sql/dbo/User Defined Types/SelectionReadOnlyArray.sql index 319ec8c1c4..f2e19b1a09 100644 --- a/src/Sql/dbo/User Defined Types/SelectionReadOnlyArray.sql +++ b/src/Sql/dbo/User Defined Types/SelectionReadOnlyArray.sql @@ -1,6 +1,5 @@ CREATE TYPE [dbo].[SelectionReadOnlyArray] AS TABLE ( [Id] UNIQUEIDENTIFIER NOT NULL, [ReadOnly] BIT NOT NULL, - [HidePasswords] BIT NOT NULL, - [Manage] BIT NOT NULL); + [HidePasswords] BIT NOT NULL); diff --git a/src/Sql/dbo_future/Functions/PolicyApplicableToUser.sql b/src/Sql/dbo_future/Functions/PolicyApplicableToUser.sql deleted file mode 100644 index a851a1e799..0000000000 --- a/src/Sql/dbo_future/Functions/PolicyApplicableToUser.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Created 2023-03 --- DELETE FILE \ No newline at end of file diff --git a/src/Sql/dbo_future/Stored Procedures/Collection_CreateWithGroups.sql b/src/Sql/dbo_future/Stored Procedures/Collection_CreateWithGroups.sql deleted file mode 100644 index 0c55ab99b7..0000000000 --- a/src/Sql/dbo_future/Stored Procedures/Collection_CreateWithGroups.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Created 2022-11 --- DELETE FILE \ No newline at end of file diff --git a/src/Sql/dbo_future/Stored Procedures/Collection_ReadWithGroupsById.sql b/src/Sql/dbo_future/Stored Procedures/Collection_ReadWithGroupsById.sql deleted file mode 100644 index 0c55ab99b7..0000000000 --- a/src/Sql/dbo_future/Stored Procedures/Collection_ReadWithGroupsById.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Created 2022-11 --- DELETE FILE \ No newline at end of file diff --git a/src/Sql/dbo_future/Stored Procedures/Collection_ReadWithGroupsByIdUserId.sql b/src/Sql/dbo_future/Stored Procedures/Collection_ReadWithGroupsByIdUserId.sql deleted file mode 100644 index 0c55ab99b7..0000000000 --- a/src/Sql/dbo_future/Stored Procedures/Collection_ReadWithGroupsByIdUserId.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Created 2022-11 --- DELETE FILE \ No newline at end of file diff --git a/src/Sql/dbo_future/Stored Procedures/Collection_UpdateWithGroups.sql b/src/Sql/dbo_future/Stored Procedures/Collection_UpdateWithGroups.sql deleted file mode 100644 index 0c55ab99b7..0000000000 --- a/src/Sql/dbo_future/Stored Procedures/Collection_UpdateWithGroups.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Created 2022-11 --- DELETE FILE \ No newline at end of file diff --git a/src/Sql/dbo_future/Stored Procedures/OrganizationUser_CreateMany.sql b/src/Sql/dbo_future/Stored Procedures/OrganizationUser_CreateMany.sql deleted file mode 100644 index 371848cf9d..0000000000 --- a/src/Sql/dbo_future/Stored Procedures/OrganizationUser_CreateMany.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Created 2023-01 --- DELETE FILE diff --git a/src/Sql/dbo_future/Stored Procedures/OrganizationUser_UpdateMany.sql b/src/Sql/dbo_future/Stored Procedures/OrganizationUser_UpdateMany.sql deleted file mode 100644 index 371848cf9d..0000000000 --- a/src/Sql/dbo_future/Stored Procedures/OrganizationUser_UpdateMany.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Created 2023-01 --- DELETE FILE diff --git a/src/Sql/dbo_future/Stored Procedures/Policy_CountByTypeApplicableToUser.sql b/src/Sql/dbo_future/Stored Procedures/Policy_CountByTypeApplicableToUser.sql deleted file mode 100644 index a851a1e799..0000000000 --- a/src/Sql/dbo_future/Stored Procedures/Policy_CountByTypeApplicableToUser.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Created 2023-03 --- DELETE FILE \ No newline at end of file diff --git a/src/Sql/dbo_future/Stored Procedures/Policy_ReadByTypeApplicableToUser.sql b/src/Sql/dbo_future/Stored Procedures/Policy_ReadByTypeApplicableToUser.sql deleted file mode 100644 index a851a1e799..0000000000 --- a/src/Sql/dbo_future/Stored Procedures/Policy_ReadByTypeApplicableToUser.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Created 2023-03 --- DELETE FILE \ No newline at end of file diff --git a/src/Sql/dbo_future/Stored Procedures/User_Create.sql b/src/Sql/dbo_future/Stored Procedures/User_Create.sql deleted file mode 100644 index 7139153950..0000000000 --- a/src/Sql/dbo_future/Stored Procedures/User_Create.sql +++ /dev/null @@ -1,120 +0,0 @@ -CREATE PROCEDURE [dbo].[User_Create] - @Id UNIQUEIDENTIFIER OUTPUT, - @Name NVARCHAR(50), - @Email NVARCHAR(256), - @EmailVerified BIT, - @MasterPassword NVARCHAR(300), - @MasterPasswordHint NVARCHAR(50), - @Culture NVARCHAR(10), - @SecurityStamp NVARCHAR(50), - @TwoFactorProviders NVARCHAR(MAX), - @TwoFactorRecoveryCode NVARCHAR(32), - @EquivalentDomains NVARCHAR(MAX), - @ExcludedGlobalEquivalentDomains NVARCHAR(MAX), - @AccountRevisionDate DATETIME2(7), - @Key NVARCHAR(MAX), - @PublicKey NVARCHAR(MAX), - @PrivateKey NVARCHAR(MAX), - @Premium BIT, - @PremiumExpirationDate DATETIME2(7), - @RenewalReminderDate DATETIME2(7), - @Storage BIGINT, - @MaxStorageGb SMALLINT, - @Gateway TINYINT, - @GatewayCustomerId VARCHAR(50), - @GatewaySubscriptionId VARCHAR(50), - @ReferenceData VARCHAR(MAX), - @LicenseKey VARCHAR(100), - @Kdf TINYINT, - @KdfIterations INT, - @CreationDate DATETIME2(7), - @RevisionDate DATETIME2(7), - @ApiKey VARCHAR(30), - @ForcePasswordReset BIT = 0, - @UsesKeyConnector BIT = 0, - @FailedLoginCount INT = 0, - @LastFailedLoginDate DATETIME2(7), - @AvatarColor VARCHAR(7) = NULL -AS -BEGIN - SET NOCOUNT ON - - INSERT INTO [dbo].[User] - ( - [Id], - [Name], - [Email], - [EmailVerified], - [MasterPassword], - [MasterPasswordHint], - [Culture], - [SecurityStamp], - [TwoFactorProviders], - [TwoFactorRecoveryCode], - [EquivalentDomains], - [ExcludedGlobalEquivalentDomains], - [AccountRevisionDate], - [Key], - [PublicKey], - [PrivateKey], - [Premium], - [PremiumExpirationDate], - [RenewalReminderDate], - [Storage], - [MaxStorageGb], - [Gateway], - [GatewayCustomerId], - [GatewaySubscriptionId], - [ReferenceData], - [LicenseKey], - [Kdf], - [KdfIterations], - [CreationDate], - [RevisionDate], - [ApiKey], - [ForcePasswordReset], - [UsesKeyConnector], - [FailedLoginCount], - [LastFailedLoginDate], - [AvatarColor] - ) - VALUES - ( - @Id, - @Name, - @Email, - @EmailVerified, - @MasterPassword, - @MasterPasswordHint, - @Culture, - @SecurityStamp, - @TwoFactorProviders, - @TwoFactorRecoveryCode, - @EquivalentDomains, - @ExcludedGlobalEquivalentDomains, - @AccountRevisionDate, - @Key, - @PublicKey, - @PrivateKey, - @Premium, - @PremiumExpirationDate, - @RenewalReminderDate, - @Storage, - @MaxStorageGb, - @Gateway, - @GatewayCustomerId, - @GatewaySubscriptionId, - @ReferenceData, - @LicenseKey, - @Kdf, - @KdfIterations, - @CreationDate, - @RevisionDate, - @ApiKey, - @ForcePasswordReset, - @UsesKeyConnector, - @FailedLoginCount, - @LastFailedLoginDate, - @AvatarColor - ) -END diff --git a/src/Sql/dbo_future/Stored Procedures/User_Update.sql b/src/Sql/dbo_future/Stored Procedures/User_Update.sql deleted file mode 100644 index 1ef314af8e..0000000000 --- a/src/Sql/dbo_future/Stored Procedures/User_Update.sql +++ /dev/null @@ -1,82 +0,0 @@ -CREATE PROCEDURE [dbo].[User_Update] - @Id UNIQUEIDENTIFIER, - @Name NVARCHAR(50), - @Email NVARCHAR(256), - @EmailVerified BIT, - @MasterPassword NVARCHAR(300), - @MasterPasswordHint NVARCHAR(50), - @Culture NVARCHAR(10), - @SecurityStamp NVARCHAR(50), - @TwoFactorProviders NVARCHAR(MAX), - @TwoFactorRecoveryCode NVARCHAR(32), - @EquivalentDomains NVARCHAR(MAX), - @ExcludedGlobalEquivalentDomains NVARCHAR(MAX), - @AccountRevisionDate DATETIME2(7), - @Key NVARCHAR(MAX), - @PublicKey NVARCHAR(MAX), - @PrivateKey NVARCHAR(MAX), - @Premium BIT, - @PremiumExpirationDate DATETIME2(7), - @RenewalReminderDate DATETIME2(7), - @Storage BIGINT, - @MaxStorageGb SMALLINT, - @Gateway TINYINT, - @GatewayCustomerId VARCHAR(50), - @GatewaySubscriptionId VARCHAR(50), - @ReferenceData VARCHAR(MAX), - @LicenseKey VARCHAR(100), - @Kdf TINYINT, - @KdfIterations INT, - @CreationDate DATETIME2(7), - @RevisionDate DATETIME2(7), - @ApiKey VARCHAR(30), - @ForcePasswordReset BIT = 0, - @UsesKeyConnector BIT = 0, - @FailedLoginCount INT, - @LastFailedLoginDate DATETIME2(7), - @AvatarColor VARCHAR(7) -AS -BEGIN - SET NOCOUNT ON - - UPDATE - [dbo].[User] - SET - [Name] = @Name, - [Email] = @Email, - [EmailVerified] = @EmailVerified, - [MasterPassword] = @MasterPassword, - [MasterPasswordHint] = @MasterPasswordHint, - [Culture] = @Culture, - [SecurityStamp] = @SecurityStamp, - [TwoFactorProviders] = @TwoFactorProviders, - [TwoFactorRecoveryCode] = @TwoFactorRecoveryCode, - [EquivalentDomains] = @EquivalentDomains, - [ExcludedGlobalEquivalentDomains] = @ExcludedGlobalEquivalentDomains, - [AccountRevisionDate] = @AccountRevisionDate, - [Key] = @Key, - [PublicKey] = @PublicKey, - [PrivateKey] = @PrivateKey, - [Premium] = @Premium, - [PremiumExpirationDate] = @PremiumExpirationDate, - [RenewalReminderDate] = @RenewalReminderDate, - [Storage] = @Storage, - [MaxStorageGb] = @MaxStorageGb, - [Gateway] = @Gateway, - [GatewayCustomerId] = @GatewayCustomerId, - [GatewaySubscriptionId] = @GatewaySubscriptionId, - [ReferenceData] = @ReferenceData, - [LicenseKey] = @LicenseKey, - [Kdf] = @Kdf, - [KdfIterations] = @KdfIterations, - [CreationDate] = @CreationDate, - [RevisionDate] = @RevisionDate, - [ApiKey] = @ApiKey, - [ForcePasswordReset] = @ForcePasswordReset, - [UsesKeyConnector] = @UsesKeyConnector, - [FailedLoginCount] = @FailedLoginCount, - [LastFailedLoginDate] = @LastFailedLoginDate, - [AvatarColor] = @AvatarColor - WHERE - [Id] = @Id -END diff --git a/src/Sql/dbo_future/Tables/User.sql b/src/Sql/dbo_future/Tables/User.sql deleted file mode 100644 index e2d681d1e2..0000000000 --- a/src/Sql/dbo_future/Tables/User.sql +++ /dev/null @@ -1,49 +0,0 @@ -CREATE TABLE [dbo].[User] ( - [Id] UNIQUEIDENTIFIER NOT NULL, - [Name] NVARCHAR (50) NULL, - [Email] NVARCHAR (256) NOT NULL, - [EmailVerified] BIT NOT NULL, - [MasterPassword] NVARCHAR (300) NULL, - [MasterPasswordHint] NVARCHAR (50) NULL, - [Culture] NVARCHAR (10) NOT NULL, - [SecurityStamp] NVARCHAR (50) NOT NULL, - [TwoFactorProviders] NVARCHAR (MAX) NULL, - [TwoFactorRecoveryCode] NVARCHAR (32) NULL, - [EquivalentDomains] NVARCHAR (MAX) NULL, - [ExcludedGlobalEquivalentDomains] NVARCHAR (MAX) NULL, - [AccountRevisionDate] DATETIME2 (7) NOT NULL, - [Key] VARCHAR (MAX) NULL, - [PublicKey] VARCHAR (MAX) NULL, - [PrivateKey] VARCHAR (MAX) NULL, - [Premium] BIT NOT NULL, - [PremiumExpirationDate] DATETIME2 (7) NULL, - [RenewalReminderDate] DATETIME2 (7) NULL, - [Storage] BIGINT NULL, - [MaxStorageGb] SMALLINT NULL, - [Gateway] TINYINT NULL, - [GatewayCustomerId] VARCHAR (50) NULL, - [GatewaySubscriptionId] VARCHAR (50) NULL, - [ReferenceData] NVARCHAR (MAX) NULL, - [LicenseKey] VARCHAR (100) NULL, - [Kdf] TINYINT NOT NULL, - [KdfIterations] INT NOT NULL, - [CreationDate] DATETIME2 (7) NOT NULL, - [RevisionDate] DATETIME2 (7) NOT NULL, - [ApiKey] VARCHAR (30) NOT NULL, - [ForcePasswordReset] BIT NOT NULL, - [UsesKeyConnector] BIT NOT NULL, - [FailedLoginCount] INT CONSTRAINT [D_User_FailedLoginCount] DEFAULT ((0)) NOT NULL, - [LastFailedLoginDate] DATETIME2 (7) NULL, - [AvatarColor] VARCHAR(7) NULL, - CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ([Id] ASC) -); - - -GO -CREATE UNIQUE NONCLUSTERED INDEX [IX_User_Email] - ON [dbo].[User]([Email] ASC); - -GO -CREATE NONCLUSTERED INDEX [IX_User_Premium_PremiumExpirationDate_RenewalReminderDate] - ON [dbo].[User]([Premium] ASC, [PremiumExpirationDate] ASC, [RenewalReminderDate] ASC); - diff --git a/src/Sql/dbo_future/User Defined Types/OrganizationUserType.sql b/src/Sql/dbo_future/User Defined Types/OrganizationUserType.sql deleted file mode 100644 index 371848cf9d..0000000000 --- a/src/Sql/dbo_future/User Defined Types/OrganizationUserType.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Created 2023-01 --- DELETE FILE diff --git a/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsManagerEventsControllerTests.cs b/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsManagerEventsControllerTests.cs new file mode 100644 index 0000000000..4c053c3a2e --- /dev/null +++ b/test/Api.IntegrationTest/SecretsManager/Controllers/SecretsManagerEventsControllerTests.cs @@ -0,0 +1,71 @@ +using System.Net; +using System.Net.Http.Headers; +using Bit.Api.IntegrationTest.Factories; +using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Repositories; +using Xunit; + +namespace Bit.Api.IntegrationTest.SecretsManager.Controllers; + +public class SecretsManagerEventsControllerTests : IClassFixture, IAsyncLifetime +{ + private const string _mockEncryptedString = + "2.3Uk+WNBIoU5xzmVFNcoWzz==|1MsPIYuRfdOHfu/0uY6H2Q==|/98sp4wb6pHP1VTZ9JcNCYgQjEUMFPlqJgCwRk1YXKg="; + + private readonly HttpClient _client; + private readonly ApiApplicationFactory _factory; + + private readonly IServiceAccountRepository _serviceAccountRepository; + + private string _email = null!; + private SecretsManagerOrganizationHelper _organizationHelper = null!; + + public SecretsManagerEventsControllerTests(ApiApplicationFactory factory) + { + _factory = factory; + _client = _factory.CreateClient(); + _serviceAccountRepository = _factory.GetService(); + } + + public async Task InitializeAsync() + { + _email = $"integration-test{Guid.NewGuid()}@bitwarden.com"; + await _factory.LoginWithNewAccount(_email); + _organizationHelper = new SecretsManagerOrganizationHelper(_factory, _email); + } + + public Task DisposeAsync() + { + _client.Dispose(); + return Task.CompletedTask; + } + + private async Task LoginAsync(string email) + { + var tokens = await _factory.LoginAsync(email); + _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.Token); + } + + [Theory] + [InlineData(false, false, false)] + [InlineData(false, false, true)] + [InlineData(false, true, false)] + [InlineData(false, true, true)] + [InlineData(true, false, false)] + [InlineData(true, false, true)] + [InlineData(true, true, false)] + public async Task GetServiceAccountEvents_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled) + { + var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled); + await LoginAsync(_email); + + var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount + { + OrganizationId = org.Id, + Name = _mockEncryptedString + }); + + var response = await _client.GetAsync($"/sm/events/service-accounts/{serviceAccount.Id}"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } +} diff --git a/test/Api.Test/Controllers/GroupsControllerTests.cs b/test/Api.Test/AdminConsole/Controllers/GroupsControllerTests.cs similarity index 91% rename from test/Api.Test/Controllers/GroupsControllerTests.cs rename to test/Api.Test/AdminConsole/Controllers/GroupsControllerTests.cs index 8a8b65546d..9777cb9e66 100644 --- a/test/Api.Test/Controllers/GroupsControllerTests.cs +++ b/test/Api.Test/AdminConsole/Controllers/GroupsControllerTests.cs @@ -1,16 +1,18 @@ -using Bit.Api.Controllers; -using Bit.Api.Models.Request; +using Bit.Api.AdminConsole.Controllers; +using Bit.Api.AdminConsole.Models.Request; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Models.Data; -using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.Repositories; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; -namespace Bit.Api.Test.Controllers; +namespace Bit.Api.Test.AdminConsole.Controllers; [ControllerCustomize(typeof(GroupsController))] [SutProviderCustomize] diff --git a/test/Api.Test/Controllers/OrganizationSponsorshipsControllerTests.cs b/test/Api.Test/AdminConsole/Controllers/OrganizationSponsorshipsControllerTests.cs similarity index 99% rename from test/Api.Test/Controllers/OrganizationSponsorshipsControllerTests.cs rename to test/Api.Test/AdminConsole/Controllers/OrganizationSponsorshipsControllerTests.cs index e58add5ef0..e54a896885 100644 --- a/test/Api.Test/Controllers/OrganizationSponsorshipsControllerTests.cs +++ b/test/Api.Test/AdminConsole/Controllers/OrganizationSponsorshipsControllerTests.cs @@ -13,7 +13,7 @@ using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; -namespace Bit.Api.Test.Controllers; +namespace Bit.Api.Test.AdminConsole.Controllers; [ControllerCustomize(typeof(OrganizationSponsorshipsController))] [SutProviderCustomize] diff --git a/test/Api.Test/Controllers/OrganizationUsersControllerTests.cs b/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs similarity index 96% rename from test/Api.Test/Controllers/OrganizationUsersControllerTests.cs rename to test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs index 6053d17ddb..4b5c633cbe 100644 --- a/test/Api.Test/Controllers/OrganizationUsersControllerTests.cs +++ b/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs @@ -1,5 +1,5 @@ -using Bit.Api.Controllers; -using Bit.Api.Models.Request.Organizations; +using Bit.Api.AdminConsole.Controllers; +using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Core.Entities; using Bit.Core.Models.Data.Organizations.Policies; using Bit.Core.Repositories; @@ -10,7 +10,7 @@ using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; -namespace Bit.Api.Test.Controllers; +namespace Bit.Api.Test.AdminConsole.Controllers; [ControllerCustomize(typeof(OrganizationUsersController))] [SutProviderCustomize] diff --git a/test/Api.Test/Controllers/OrganizationsControllerTests.cs b/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs similarity index 98% rename from test/Api.Test/Controllers/OrganizationsControllerTests.cs rename to test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs index 8f8f9b20d9..2568ba518a 100644 --- a/test/Api.Test/Controllers/OrganizationsControllerTests.cs +++ b/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs @@ -1,6 +1,6 @@ using System.Security.Claims; using AutoFixture.Xunit2; -using Bit.Api.Controllers; +using Bit.Api.AdminConsole.Controllers; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models.Data; @@ -18,7 +18,7 @@ using Bit.Core.Settings; using NSubstitute; using Xunit; -namespace Bit.Api.Test.Controllers; +namespace Bit.Api.Test.AdminConsole.Controllers; public class OrganizationsControllerTests : IDisposable { diff --git a/test/Api.Test/Public/Controllers/GroupsControllerTests.cs b/test/Api.Test/AdminConsole/Public/Controllers/GroupsControllerTests.cs similarity index 89% rename from test/Api.Test/Public/Controllers/GroupsControllerTests.cs rename to test/Api.Test/AdminConsole/Public/Controllers/GroupsControllerTests.cs index fe268ff1e0..37351b3255 100644 --- a/test/Api.Test/Public/Controllers/GroupsControllerTests.cs +++ b/test/Api.Test/AdminConsole/Public/Controllers/GroupsControllerTests.cs @@ -1,10 +1,12 @@ -using Bit.Api.Models.Public.Request; -using Bit.Api.Models.Public.Response; -using Bit.Api.Public.Controllers; +using Bit.Api.AdminConsole.Public.Controllers; +using Bit.Api.AdminConsole.Public.Models.Request; +using Bit.Api.AdminConsole.Public.Models.Response; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Models.Data; -using Bit.Core.OrganizationFeatures.Groups.Interfaces; using Bit.Core.Repositories; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; @@ -12,7 +14,7 @@ using Microsoft.AspNetCore.Mvc; using NSubstitute; using Xunit; -namespace Bit.Api.Test.Public.Controllers; +namespace Bit.Api.Test.AdminConsole.Public.Controllers; [ControllerCustomize(typeof(GroupsController))] [SutProviderCustomize] diff --git a/test/Api.Test/Controllers/OrganizationConnectionsControllerTests.cs b/test/Api.Test/Controllers/OrganizationConnectionsControllerTests.cs index c95d6861f6..bbeb50b182 100644 --- a/test/Api.Test/Controllers/OrganizationConnectionsControllerTests.cs +++ b/test/Api.Test/Controllers/OrganizationConnectionsControllerTests.cs @@ -143,10 +143,10 @@ public class OrganizationConnectionsControllerTests public async Task UpdateConnection_RequiresOwnerPermissions(SutProvider sutProvider) { sutProvider.GetDependency() - .GetByIdAsync(Arg.Any()) + .GetByIdOrganizationIdAsync(Arg.Any(), Arg.Any()) .Returns(new OrganizationConnection()); - var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateConnection(default, null)); + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateConnection(default, new OrganizationConnectionRequestModel())); Assert.Contains("You do not have permission to update this connection.", exception.Message); } @@ -164,8 +164,8 @@ public class OrganizationConnectionsControllerTests sutProvider.GetDependency().OrganizationOwner(typedModel.OrganizationId).Returns(true); var orgConnectionRepository = sutProvider.GetDependency(); - orgConnectionRepository.GetByIdAsync(existing1.Id).Returns(existing1); - orgConnectionRepository.GetByIdAsync(existing2.Id).Returns(existing2); + orgConnectionRepository.GetByIdOrganizationIdAsync(existing1.Id, existing1.OrganizationId).Returns(existing1); + orgConnectionRepository.GetByIdOrganizationIdAsync(existing2.Id, existing2.OrganizationId).Returns(existing2); orgConnectionRepository.GetByOrganizationIdTypeAsync(typedModel.OrganizationId, type).Returns(new[] { existing1, existing2 }); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.UpdateConnection(existing1.Id, typedModel)); @@ -186,7 +186,7 @@ public class OrganizationConnectionsControllerTests sutProvider.GetDependency().OrganizationOwner(typedModel.OrganizationId).Returns(true); sutProvider.GetDependency() - .GetByIdAsync(existing1.Id) + .GetByIdOrganizationIdAsync(existing1.Id, existing1.OrganizationId) .Returns(existing1); sutProvider.GetDependency().ManageScim(typedModel.OrganizationId).Returns(true); @@ -212,6 +212,7 @@ public class OrganizationConnectionsControllerTests }); updated.Config = JsonSerializer.Serialize(config); updated.Id = existing.Id; + updated.OrganizationId = existing.OrganizationId; updated.Type = OrganizationConnectionType.CloudBillingSync; var model = RequestModelFromEntity(updated); @@ -224,7 +225,7 @@ public class OrganizationConnectionsControllerTests .UpdateAsync(default) .ReturnsForAnyArgs(updated); sutProvider.GetDependency() - .GetByIdAsync(existing.Id) + .GetByIdOrganizationIdAsync(existing.Id, existing.OrganizationId) .Returns(existing); OrganizationLicense organizationLicense = new OrganizationLicense(); @@ -264,6 +265,7 @@ public class OrganizationConnectionsControllerTests }); updated.Config = JsonSerializer.Serialize(config); updated.Id = existing.Id; + updated.OrganizationId = existing.OrganizationId; updated.Type = OrganizationConnectionType.CloudBillingSync; var model = RequestModelFromEntity(updated); sutProvider.GetDependency().SelfHosted.Returns(true); @@ -275,7 +277,7 @@ public class OrganizationConnectionsControllerTests .UpdateAsync(default) .ReturnsForAnyArgs(updated); sutProvider.GetDependency() - .GetByIdAsync(existing.Id) + .GetByIdOrganizationIdAsync(existing.Id, existing.OrganizationId) .Returns(existing); OrganizationLicense organizationLicense = new OrganizationLicense(); diff --git a/test/Api.Test/Controllers/OrganizationDomainControllerTests.cs b/test/Api.Test/Controllers/OrganizationDomainControllerTests.cs index bd97aa20bf..019dbdcba0 100644 --- a/test/Api.Test/Controllers/OrganizationDomainControllerTests.cs +++ b/test/Api.Test/Controllers/OrganizationDomainControllerTests.cs @@ -4,6 +4,7 @@ using Bit.Api.Models.Request.Organizations; using Bit.Api.Models.Response; using Bit.Api.Models.Response.Organizations; using Bit.Core.Context; +using Bit.Core.Entities; using Bit.Core.Exceptions; using Bit.Core.Models.Data.Organizations; using Bit.Core.OrganizationFeatures.OrganizationDomains.Interfaces; @@ -13,8 +14,6 @@ using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using NSubstitute.ReturnsExtensions; using Xunit; -using Organization = Bit.Core.Entities.Organization; -using OrganizationDomain = Bit.Core.Entities.OrganizationDomain; namespace Bit.Api.Test.Controllers; @@ -28,7 +27,7 @@ public class OrganizationDomainControllerTests { sutProvider.GetDependency().ManageSso(orgId).Returns(false); - var requestAction = async () => await sutProvider.Sut.Get(orgId.ToString()); + var requestAction = async () => await sutProvider.Sut.Get(orgId); await Assert.ThrowsAsync(requestAction); } @@ -40,7 +39,7 @@ public class OrganizationDomainControllerTests sutProvider.GetDependency().ManageSso(orgId).Returns(true); sutProvider.GetDependency().GetByIdAsync(orgId).ReturnsNull(); - var requestAction = async () => await sutProvider.Sut.Get(orgId.ToString()); + var requestAction = async () => await sutProvider.Sut.Get(orgId); await Assert.ThrowsAsync(requestAction); } @@ -52,7 +51,7 @@ public class OrganizationDomainControllerTests sutProvider.GetDependency().ManageSso(orgId).Returns(true); sutProvider.GetDependency().GetByIdAsync(orgId).Returns(new Organization()); sutProvider.GetDependency() - .GetDomainsByOrganizationId(orgId).Returns(new List + .GetDomainsByOrganizationIdAsync(orgId).Returns(new List { new() { @@ -64,7 +63,7 @@ public class OrganizationDomainControllerTests } }); - var result = await sutProvider.Sut.Get(orgId.ToString()); + var result = await sutProvider.Sut.Get(orgId); Assert.IsType>(result); Assert.Equal(orgId, result.Data.Select(x => x.OrganizationId).FirstOrDefault()); @@ -76,7 +75,7 @@ public class OrganizationDomainControllerTests { sutProvider.GetDependency().ManageSso(orgId).Returns(false); - var requestAction = async () => await sutProvider.Sut.Get(orgId.ToString(), id.ToString()); + var requestAction = async () => await sutProvider.Sut.Get(orgId, id); await Assert.ThrowsAsync(requestAction); } @@ -88,7 +87,7 @@ public class OrganizationDomainControllerTests sutProvider.GetDependency().ManageSso(orgId).Returns(true); sutProvider.GetDependency().GetByIdAsync(orgId).ReturnsNull(); - var requestAction = async () => await sutProvider.Sut.Get(orgId.ToString(), id.ToString()); + var requestAction = async () => await sutProvider.Sut.Get(orgId, id); await Assert.ThrowsAsync(requestAction); } @@ -99,9 +98,24 @@ public class OrganizationDomainControllerTests { sutProvider.GetDependency().ManageSso(orgId).Returns(true); sutProvider.GetDependency().GetByIdAsync(orgId).Returns(new Organization()); - sutProvider.GetDependency().GetOrganizationDomainById(id).ReturnsNull(); + sutProvider.GetDependency().GetOrganizationDomainByIdOrganizationIdAsync(id, orgId).ReturnsNull(); - var requestAction = async () => await sutProvider.Sut.Get(orgId.ToString(), id.ToString()); + var requestAction = async () => await sutProvider.Sut.Get(orgId, id); + + await Assert.ThrowsAsync(requestAction); + } + + [Theory, BitAutoData] + public async Task GetByOrgIdAndId_ShouldThrowNotFound_WhenOrgIdDoesNotMatch(OrganizationDomain organizationDomain, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(organizationDomain.OrganizationId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(organizationDomain.OrganizationId).Returns(new Organization()); + sutProvider.GetDependency() + .GetDomainByIdOrganizationIdAsync(organizationDomain.Id, organizationDomain.OrganizationId) + .ReturnsNull(); + + var requestAction = async () => await sutProvider.Sut.Get(organizationDomain.OrganizationId, organizationDomain.Id); await Assert.ThrowsAsync(requestAction); } @@ -112,7 +126,7 @@ public class OrganizationDomainControllerTests { sutProvider.GetDependency().ManageSso(orgId).Returns(true); sutProvider.GetDependency().GetByIdAsync(orgId).Returns(new Organization()); - sutProvider.GetDependency().GetOrganizationDomainById(id) + sutProvider.GetDependency().GetOrganizationDomainByIdOrganizationIdAsync(id, orgId) .Returns(new OrganizationDomain { Id = Guid.NewGuid(), @@ -122,7 +136,7 @@ public class OrganizationDomainControllerTests Txt = "btw+12342" }); - var result = await sutProvider.Sut.Get(orgId.ToString(), id.ToString()); + var result = await sutProvider.Sut.Get(orgId, id); Assert.IsType(result); Assert.Equal(orgId, result.OrganizationId); @@ -134,7 +148,7 @@ public class OrganizationDomainControllerTests { sutProvider.GetDependency().ManageSso(orgId).Returns(false); - var requestAction = async () => await sutProvider.Sut.Post(orgId.ToString(), model); + var requestAction = async () => await sutProvider.Sut.Post(orgId, model); await Assert.ThrowsAsync(requestAction); } @@ -146,7 +160,7 @@ public class OrganizationDomainControllerTests sutProvider.GetDependency().ManageSso(orgId).Returns(true); sutProvider.GetDependency().GetByIdAsync(orgId).ReturnsNull(); - var requestAction = async () => await sutProvider.Sut.Post(orgId.ToString(), model); + var requestAction = async () => await sutProvider.Sut.Post(orgId, model); await Assert.ThrowsAsync(requestAction); } @@ -160,7 +174,7 @@ public class OrganizationDomainControllerTests sutProvider.GetDependency().CreateAsync(Arg.Any()) .Returns(new OrganizationDomain()); - var result = await sutProvider.Sut.Post(orgId.ToString(), model); + var result = await sutProvider.Sut.Post(orgId, model); await sutProvider.GetDependency().ReceivedWithAnyArgs(1) .CreateAsync(Arg.Any()); @@ -173,7 +187,7 @@ public class OrganizationDomainControllerTests { sutProvider.GetDependency().ManageSso(orgId).Returns(false); - var requestAction = async () => await sutProvider.Sut.Verify(orgId.ToString(), id.ToString()); + var requestAction = async () => await sutProvider.Sut.Verify(orgId, id); await Assert.ThrowsAsync(requestAction); } @@ -185,24 +199,42 @@ public class OrganizationDomainControllerTests sutProvider.GetDependency().ManageSso(orgId).Returns(true); sutProvider.GetDependency().GetByIdAsync(orgId).ReturnsNull(); - var requestAction = async () => await sutProvider.Sut.Verify(orgId.ToString(), id.ToString()); + var requestAction = async () => await sutProvider.Sut.Verify(orgId, id); await Assert.ThrowsAsync(requestAction); } [Theory, BitAutoData] - public async Task Verify_WhenRequestIsValid(Guid orgId, Guid id, + public async Task VerifyOrganizationDomain_ShouldThrowNotFound_WhenOrgIdDoesNotMatch(OrganizationDomain organizationDomain, SutProvider sutProvider) { - sutProvider.GetDependency().ManageSso(orgId).Returns(true); - sutProvider.GetDependency().GetByIdAsync(orgId).Returns(new Organization()); - sutProvider.GetDependency().VerifyOrganizationDomain(id) + sutProvider.GetDependency().ManageSso(organizationDomain.OrganizationId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(organizationDomain.OrganizationId).Returns(new Organization()); + sutProvider.GetDependency() + .GetDomainByIdOrganizationIdAsync(organizationDomain.Id, organizationDomain.OrganizationId) + .ReturnsNull(); + + var requestAction = async () => await sutProvider.Sut.Verify(organizationDomain.OrganizationId, organizationDomain.Id); + + await Assert.ThrowsAsync(requestAction); + } + + [Theory, BitAutoData] + public async Task Verify_WhenRequestIsValid(OrganizationDomain organizationDomain, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(organizationDomain.OrganizationId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(organizationDomain.OrganizationId).Returns(new Organization()); + sutProvider.GetDependency() + .GetDomainByIdOrganizationIdAsync(organizationDomain.Id, organizationDomain.OrganizationId) + .Returns(organizationDomain); + sutProvider.GetDependency().VerifyOrganizationDomainAsync(organizationDomain) .Returns(new OrganizationDomain()); - var result = await sutProvider.Sut.Verify(orgId.ToString(), id.ToString()); + var result = await sutProvider.Sut.Verify(organizationDomain.OrganizationId, organizationDomain.Id); await sutProvider.GetDependency().Received(1) - .VerifyOrganizationDomain(id); + .VerifyOrganizationDomainAsync(organizationDomain); Assert.IsType(result); } @@ -212,7 +244,7 @@ public class OrganizationDomainControllerTests { sutProvider.GetDependency().ManageSso(orgId).Returns(false); - var requestAction = async () => await sutProvider.Sut.RemoveDomain(orgId.ToString(), id.ToString()); + var requestAction = async () => await sutProvider.Sut.RemoveDomain(orgId, id); await Assert.ThrowsAsync(requestAction); } @@ -224,22 +256,40 @@ public class OrganizationDomainControllerTests sutProvider.GetDependency().ManageSso(orgId).Returns(true); sutProvider.GetDependency().GetByIdAsync(orgId).ReturnsNull(); - var requestAction = async () => await sutProvider.Sut.RemoveDomain(orgId.ToString(), id.ToString()); + var requestAction = async () => await sutProvider.Sut.RemoveDomain(orgId, id); await Assert.ThrowsAsync(requestAction); } [Theory, BitAutoData] - public async Task RemoveDomain_WhenRequestIsValid(Guid orgId, Guid id, + public async Task RemoveDomain_ShouldThrowNotFound_WhenOrgIdDoesNotMatch(OrganizationDomain organizationDomain, SutProvider sutProvider) { - sutProvider.GetDependency().ManageSso(orgId).Returns(true); - sutProvider.GetDependency().GetByIdAsync(orgId).Returns(new Organization()); + sutProvider.GetDependency().ManageSso(organizationDomain.OrganizationId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(organizationDomain.OrganizationId).Returns(new Organization()); + sutProvider.GetDependency() + .GetDomainByIdOrganizationIdAsync(organizationDomain.Id, organizationDomain.OrganizationId) + .ReturnsNull(); - await sutProvider.Sut.RemoveDomain(orgId.ToString(), id.ToString()); + var requestAction = async () => await sutProvider.Sut.RemoveDomain(organizationDomain.OrganizationId, organizationDomain.Id); + + await Assert.ThrowsAsync(requestAction); + } + + [Theory, BitAutoData] + public async Task RemoveDomain_WhenRequestIsValid(OrganizationDomain organizationDomain, + SutProvider sutProvider) + { + sutProvider.GetDependency().ManageSso(organizationDomain.OrganizationId).Returns(true); + sutProvider.GetDependency().GetByIdAsync(organizationDomain.OrganizationId).Returns(new Organization()); + sutProvider.GetDependency() + .GetDomainByIdOrganizationIdAsync(organizationDomain.Id, organizationDomain.OrganizationId) + .Returns(organizationDomain); + + await sutProvider.Sut.RemoveDomain(organizationDomain.OrganizationId, organizationDomain.Id); await sutProvider.GetDependency().Received(1) - .DeleteAsync(id); + .DeleteAsync(organizationDomain); } [Theory, BitAutoData] diff --git a/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs index e369120a95..6c3d85e425 100644 --- a/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs +++ b/test/Api.Test/SecretsManager/Controllers/AccessPoliciesControllerTests.cs @@ -2,8 +2,9 @@ using Bit.Api.SecretsManager.Controllers; using Bit.Api.SecretsManager.Models.Request; using Bit.Api.Test.SecretsManager.Enums; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Context; -using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Repositories; diff --git a/test/Api.Test/SecretsManager/Controllers/SecretsManagerEventsControllerTests.cs b/test/Api.Test/SecretsManager/Controllers/SecretsManagerEventsControllerTests.cs new file mode 100644 index 0000000000..63eecf1482 --- /dev/null +++ b/test/Api.Test/SecretsManager/Controllers/SecretsManagerEventsControllerTests.cs @@ -0,0 +1,79 @@ +using System.Security.Claims; +using Bit.Api.SecretsManager.Controllers; +using Bit.Core.Exceptions; +using Bit.Core.Models.Data; +using Bit.Core.Repositories; +using Bit.Core.SecretsManager.Entities; +using Bit.Core.SecretsManager.Repositories; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Microsoft.AspNetCore.Authorization; +using NSubstitute; +using Xunit; + +namespace Bit.Api.Test.SecretsManager.Controllers; + +[ControllerCustomize(typeof(SecretsManagerEventsController))] +[SutProviderCustomize] +[JsonDocumentCustomize] +public class SecretsManagerEventsControllerTests +{ + [Theory] + [BitAutoData] + public async void GetServiceAccountEvents_NoAccess_Throws(SutProvider sutProvider, + ServiceAccount data) + { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), data, + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Failed()); + + + await Assert.ThrowsAsync(() => sutProvider.Sut.GetServiceAccountEventsAsync(data.Id)); + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .GetManyByOrganizationServiceAccountAsync(Arg.Any(), Arg.Any(), Arg.Any(), + Arg.Any(), Arg.Any()); + } + + [Theory] + [BitAutoData] + public async void GetServiceAccountEvents_DateRangeOver_Throws( + SutProvider sutProvider, + ServiceAccount data) + { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), data, + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Success()); + + var start = DateTime.UtcNow.AddYears(-1); + var end = DateTime.UtcNow.AddYears(1); + + await Assert.ThrowsAsync(() => + sutProvider.Sut.GetServiceAccountEventsAsync(data.Id, start, end)); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .GetManyByOrganizationServiceAccountAsync(Arg.Any(), Arg.Any(), Arg.Any(), + Arg.Any(), Arg.Any()); + } + + [Theory] + [BitAutoData] + public async void GetServiceAccountEvents_Success(SutProvider sutProvider, + ServiceAccount data) + { + sutProvider.GetDependency().GetByIdAsync(default).ReturnsForAnyArgs(data); + sutProvider.GetDependency() + .AuthorizeAsync(Arg.Any(), data, + Arg.Any>()).ReturnsForAnyArgs(AuthorizationResult.Success()); + sutProvider.GetDependency() + .GetManyByOrganizationServiceAccountAsync(default, default, default, default, default) + .ReturnsForAnyArgs(new PagedResult()); + + await sutProvider.Sut.GetServiceAccountEventsAsync(data.Id); + + await sutProvider.GetDependency().Received(1) + .GetManyByOrganizationServiceAccountAsync(data.OrganizationId, data.Id, Arg.Any(), + Arg.Any(), Arg.Any()); + } +} diff --git a/test/Billing.Test/Services/StripeEventServiceTests.cs b/test/Billing.Test/Services/StripeEventServiceTests.cs index 5b1642413d..1e4d6c2641 100644 --- a/test/Billing.Test/Services/StripeEventServiceTests.cs +++ b/test/Billing.Test/Services/StripeEventServiceTests.cs @@ -3,6 +3,7 @@ using Bit.Billing.Services.Implementations; using Bit.Billing.Test.Utilities; using Bit.Core.Settings; using FluentAssertions; +using Microsoft.Extensions.Logging; using NSubstitute; using Stripe; using Xunit; @@ -21,7 +22,7 @@ public class StripeEventServiceTests globalSettings.BaseServiceUri = baseServiceUriSettings; _stripeFacade = Substitute.For(); - _stripeEventService = new StripeEventService(globalSettings, _stripeFacade); + _stripeEventService = new StripeEventService(globalSettings, Substitute.For>(), _stripeFacade); } #region GetCharge @@ -465,12 +466,10 @@ public class StripeEventServiceTests var customer = await GetCustomerAsync(); - invoice.Customer = customer; - - _stripeFacade.GetInvoice( - invoice.Id, - Arg.Any()) - .Returns(invoice); + _stripeFacade.GetCustomer( + invoice.CustomerId, + Arg.Any()) + .Returns(customer); // Act var cloudRegionValid = await _stripeEventService.ValidateCloudRegion(stripeEvent); @@ -478,9 +477,9 @@ public class StripeEventServiceTests // Assert cloudRegionValid.Should().BeTrue(); - await _stripeFacade.Received(1).GetInvoice( - invoice.Id, - Arg.Any(), + await _stripeFacade.Received(1).GetCustomer( + invoice.CustomerId, + Arg.Any(), Arg.Any(), Arg.Any()); } diff --git a/test/Core.Test/OrganizationFeatures/Groups/CreateGroupCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommandTests.cs similarity index 95% rename from test/Core.Test/OrganizationFeatures/Groups/CreateGroupCommandTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommandTests.cs index 92011e49f2..6d521887a0 100644 --- a/test/Core.Test/OrganizationFeatures/Groups/CreateGroupCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/CreateGroupCommandTests.cs @@ -1,9 +1,10 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Data; -using Bit.Core.OrganizationFeatures.Groups; -using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Test.AutoFixture.OrganizationFixtures; using Bit.Core.Tools.Enums; @@ -15,7 +16,7 @@ using Bit.Test.Common.Helpers; using NSubstitute; using Xunit; -namespace Bit.Core.Test.OrganizationFeatures.Groups; +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Groups; [SutProviderCustomize] public class CreateGroupCommandTests diff --git a/test/Core.Test/OrganizationFeatures/Groups/DeleteGroupCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/DeleteGroupCommandTests.cs similarity index 93% rename from test/Core.Test/OrganizationFeatures/Groups/DeleteGroupCommandTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Groups/DeleteGroupCommandTests.cs index 27b070d213..fcd63203f8 100644 --- a/test/Core.Test/OrganizationFeatures/Groups/DeleteGroupCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/DeleteGroupCommandTests.cs @@ -1,8 +1,8 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups; +using Bit.Core.AdminConsole.Repositories; using Bit.Core.Enums; using Bit.Core.Exceptions; -using Bit.Core.OrganizationFeatures.Groups; -using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Test.AutoFixture.OrganizationFixtures; using Bit.Test.Common.AutoFixture; @@ -10,7 +10,7 @@ using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; -namespace Bit.Core.Test.OrganizationFeatures.Groups; +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Groups; [SutProviderCustomize] public class DeleteGroupCommandTests @@ -42,7 +42,7 @@ public class DeleteGroupCommandTests { sutProvider.GetDependency() .GetByIdAsync(groupId) - .Returns(new Core.Entities.Group + .Returns(new Group { Id = groupId, OrganizationId = Guid.NewGuid() diff --git a/test/Core.Test/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs similarity index 94% rename from test/Core.Test/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs rename to test/Core.Test/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs index dde281b785..67ca9ffada 100644 --- a/test/Core.Test/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Groups/UpdateGroupCommandTests.cs @@ -1,9 +1,10 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.OrganizationFeatures.Groups; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Data; -using Bit.Core.OrganizationFeatures.Groups; -using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Test.AutoFixture.OrganizationFixtures; using Bit.Test.Common.AutoFixture; @@ -12,7 +13,7 @@ using Bit.Test.Common.Helpers; using NSubstitute; using Xunit; -namespace Bit.Core.Test.OrganizationFeatures.Groups; +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Groups; [SutProviderCustomize] public class UpdateGroupCommandTests diff --git a/test/Core.Test/Services/GroupServiceTests.cs b/test/Core.Test/AdminConsole/Services/GroupServiceTests.cs similarity index 95% rename from test/Core.Test/Services/GroupServiceTests.cs rename to test/Core.Test/AdminConsole/Services/GroupServiceTests.cs index e904ea7a75..46d6dc4793 100644 --- a/test/Core.Test/Services/GroupServiceTests.cs +++ b/test/Core.Test/AdminConsole/Services/GroupServiceTests.cs @@ -1,4 +1,7 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.AdminConsole.Services.Implementations; +using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Repositories; @@ -9,7 +12,7 @@ using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using Xunit; -namespace Bit.Core.Test.Services; +namespace Bit.Core.Test.AdminConsole.Services; [SutProviderCustomize] [OrganizationCustomize(UseGroups = true)] diff --git a/test/Core.Test/AutoFixture/OrganizationFixtures.cs b/test/Core.Test/AutoFixture/OrganizationFixtures.cs index e4e75ed0ee..658964b73d 100644 --- a/test/Core.Test/AutoFixture/OrganizationFixtures.cs +++ b/test/Core.Test/AutoFixture/OrganizationFixtures.cs @@ -1,6 +1,7 @@ using System.Text.Json; using AutoFixture; using AutoFixture.Kernel; +using Bit.Core.AdminConsole.Entities; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models; using Bit.Core.Entities; diff --git a/test/Core.Test/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommandTests.cs index 349a1bd690..63f2bac896 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommandTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommandTests.cs @@ -1,4 +1,6 @@ -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Repositories; +using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Data; diff --git a/test/Core.Test/OrganizationFeatures/OrganizationDomains/DeleteOrganizationDomainCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationDomains/DeleteOrganizationDomainCommandTests.cs index b9201d35af..3a559d5c3d 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationDomains/DeleteOrganizationDomainCommandTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationDomains/DeleteOrganizationDomainCommandTests.cs @@ -1,13 +1,11 @@ using Bit.Core.Entities; using Bit.Core.Enums; -using Bit.Core.Exceptions; using Bit.Core.OrganizationFeatures.OrganizationDomains; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; -using NSubstitute.ReturnsExtensions; using Xunit; namespace Bit.Core.Test.OrganizationFeatures.OrganizationDomains; @@ -15,17 +13,6 @@ namespace Bit.Core.Test.OrganizationFeatures.OrganizationDomains; [SutProviderCustomize] public class DeleteOrganizationDomainCommandTests { - [Theory, BitAutoData] - public async Task DeleteAsync_ShouldThrowNotFoundException_WhenIdDoesNotExist(Guid id, - SutProvider sutProvider) - { - sutProvider.GetDependency().GetByIdAsync(id).ReturnsNull(); - - var requestAction = async () => await sutProvider.Sut.DeleteAsync(id); - - await Assert.ThrowsAsync(requestAction); - } - [Theory, BitAutoData] public async Task DeleteAsync_Success(Guid id, SutProvider sutProvider) { @@ -36,9 +23,8 @@ public class DeleteOrganizationDomainCommandTests DomainName = "Test Domain", Txt = "btw+test18383838383" }; - sutProvider.GetDependency().GetByIdAsync(id).Returns(expected); - await sutProvider.Sut.DeleteAsync(id); + await sutProvider.Sut.DeleteAsync(expected); await sutProvider.GetDependency().Received(1).DeleteAsync(expected); await sutProvider.GetDependency().Received(1) diff --git a/test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdOrganizationIdQueryTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdOrganizationIdQueryTests.cs new file mode 100644 index 0000000000..6480d8be0f --- /dev/null +++ b/test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdOrganizationIdQueryTests.cs @@ -0,0 +1,80 @@ +using Bit.Core.Entities; +using Bit.Core.OrganizationFeatures.OrganizationDomains; +using Bit.Core.Repositories; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Core.Test.OrganizationFeatures.OrganizationDomains; + +[SutProviderCustomize] +public class GetOrganizationDomainByIdOrganizationIdQueryTests +{ + [Theory, BitAutoData] + public async Task GetOrganizationDomainByIdAndOrganizationIdAsync_WithExistingParameters_ReturnsExpectedEntity( + OrganizationDomain organizationDomain, SutProvider sutProvider) + { + sutProvider.GetDependency() + .GetDomainByIdOrganizationIdAsync(organizationDomain.Id, organizationDomain.OrganizationId) + .Returns(organizationDomain); + + var result = await sutProvider.Sut.GetOrganizationDomainByIdOrganizationIdAsync(organizationDomain.Id, organizationDomain.OrganizationId); + + await sutProvider.GetDependency().Received(1) + .GetDomainByIdOrganizationIdAsync(organizationDomain.Id, organizationDomain.OrganizationId); + + Assert.Equal(organizationDomain, result); + } + + [Theory, BitAutoData] + public async Task GetOrganizationDomainByIdAndOrganizationIdAsync_WithNonExistingParameters_ReturnsNull( + Guid id, Guid organizationId, OrganizationDomain organizationDomain, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .GetDomainByIdOrganizationIdAsync(organizationDomain.Id, organizationDomain.OrganizationId) + .Returns(organizationDomain); + + var result = await sutProvider.Sut.GetOrganizationDomainByIdOrganizationIdAsync(id, organizationId); + + await sutProvider.GetDependency().Received(1) + .GetDomainByIdOrganizationIdAsync(id, organizationId); + + Assert.Null(result); + } + + [Theory, BitAutoData] + public async Task GetOrganizationDomainByIdAndOrganizationIdAsync_WithNonExistingId_ReturnsNull( + Guid id, OrganizationDomain organizationDomain, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .GetDomainByIdOrganizationIdAsync(organizationDomain.Id, organizationDomain.OrganizationId) + .Returns(organizationDomain); + + var result = await sutProvider.Sut.GetOrganizationDomainByIdOrganizationIdAsync(id, organizationDomain.OrganizationId); + + await sutProvider.GetDependency().Received(1) + .GetDomainByIdOrganizationIdAsync(id, organizationDomain.OrganizationId); + + Assert.Null(result); + } + + [Theory, BitAutoData] + public async Task GetOrganizationDomainByIdAndOrganizationIdAsync_WithNonExistingOrgId_ReturnsNull( + Guid organizationId, OrganizationDomain organizationDomain, + SutProvider sutProvider) + { + sutProvider.GetDependency() + .GetDomainByIdOrganizationIdAsync(organizationDomain.Id, organizationDomain.OrganizationId) + .Returns(organizationDomain); + + var result = await sutProvider.Sut.GetOrganizationDomainByIdOrganizationIdAsync(organizationDomain.Id, organizationId); + + await sutProvider.GetDependency().Received(1) + .GetDomainByIdOrganizationIdAsync(organizationDomain.Id, organizationId); + + Assert.Null(result); + } +} diff --git a/test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdQueryTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdQueryTests.cs deleted file mode 100644 index 0cb77d243a..0000000000 --- a/test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByIdQueryTests.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Bit.Core.OrganizationFeatures.OrganizationDomains; -using Bit.Core.Repositories; -using Bit.Test.Common.AutoFixture; -using Bit.Test.Common.AutoFixture.Attributes; -using NSubstitute; -using Xunit; - -namespace Bit.Core.Test.OrganizationFeatures.OrganizationDomains; - -[SutProviderCustomize] -public class GetOrganizationDomainByIdQueryTests -{ - [Theory, BitAutoData] - public async Task GetOrganizationDomainById_CallsGetByIdAsync(Guid id, - SutProvider sutProvider) - { - await sutProvider.Sut.GetOrganizationDomainById(id); - - await sutProvider.GetDependency().Received(1) - .GetByIdAsync(id); - } -} diff --git a/test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByOrganizationIdQueryTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByOrganizationIdQueryTests.cs index 964a3a76d4..d876676004 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByOrganizationIdQueryTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationDomains/GetOrganizationDomainByOrganizationIdQueryTests.cs @@ -14,7 +14,7 @@ public class GetOrganizationDomainByOrganizationIdQueryTests public async Task GetDomainsByOrganizationId_CallsGetDomainsByOrganizationIdAsync(Guid orgId, SutProvider sutProvider) { - await sutProvider.Sut.GetDomainsByOrganizationId(orgId); + await sutProvider.Sut.GetDomainsByOrganizationIdAsync(orgId); await sutProvider.GetDependency().Received(1) .GetDomainsByOrganizationIdAsync(orgId); diff --git a/test/Core.Test/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommandTests.cs index 54783982ad..07bc2b14e8 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommandTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationDomains/VerifyOrganizationDomainCommandTests.cs @@ -7,8 +7,6 @@ using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; -using NSubstitute.ReceivedExtensions; -using NSubstitute.ReturnsExtensions; using Xunit; namespace Bit.Core.Test.OrganizationFeatures.OrganizationDomains; @@ -16,19 +14,6 @@ namespace Bit.Core.Test.OrganizationFeatures.OrganizationDomains; [SutProviderCustomize] public class VerifyOrganizationDomainCommandTests { - [Theory, BitAutoData] - public async Task VerifyOrganizationDomain_ShouldThrowNotFound_WhenDomainDoesNotExist(Guid id, - SutProvider sutProvider) - { - sutProvider.GetDependency() - .GetByIdAsync(id) - .ReturnsNull(); - - var requestAction = async () => await sutProvider.Sut.VerifyOrganizationDomain(id); - - await Assert.ThrowsAsync(requestAction); - } - [Theory, BitAutoData] public async Task VerifyOrganizationDomain_ShouldThrowConflict_WhenDomainHasBeenClaimed(Guid id, SutProvider sutProvider) @@ -45,7 +30,7 @@ public class VerifyOrganizationDomainCommandTests .GetByIdAsync(id) .Returns(expected); - var requestAction = async () => await sutProvider.Sut.VerifyOrganizationDomain(id); + var requestAction = async () => await sutProvider.Sut.VerifyOrganizationDomainAsync(expected); var exception = await Assert.ThrowsAsync(requestAction); Assert.Contains("Domain has already been verified.", exception.Message); @@ -69,7 +54,7 @@ public class VerifyOrganizationDomainCommandTests .GetClaimedDomainsByDomainNameAsync(expected.DomainName) .Returns(new List { expected }); - var requestAction = async () => await sutProvider.Sut.VerifyOrganizationDomain(id); + var requestAction = async () => await sutProvider.Sut.VerifyOrganizationDomainAsync(expected); var exception = await Assert.ThrowsAsync(requestAction); Assert.Contains("The domain is not available to be claimed.", exception.Message); @@ -96,7 +81,7 @@ public class VerifyOrganizationDomainCommandTests .ResolveAsync(expected.DomainName, Arg.Any()) .Returns(true); - var result = await sutProvider.Sut.VerifyOrganizationDomain(id); + var result = await sutProvider.Sut.VerifyOrganizationDomainAsync(expected); Assert.NotNull(result.VerifiedDate); await sutProvider.GetDependency().Received(1) @@ -126,7 +111,7 @@ public class VerifyOrganizationDomainCommandTests .ResolveAsync(expected.DomainName, Arg.Any()) .Returns(false); - var result = await sutProvider.Sut.VerifyOrganizationDomain(id); + var result = await sutProvider.Sut.VerifyOrganizationDomainAsync(expected); Assert.Null(result.VerifiedDate); await sutProvider.GetDependency().Received(1) diff --git a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpdateSecretsManagerSubscriptionCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpdateSecretsManagerSubscriptionCommandTests.cs index e64cbe304f..16508bd25a 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpdateSecretsManagerSubscriptionCommandTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/UpdateSecretsManagerSubscriptionCommandTests.cs @@ -54,7 +54,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests var plan = StaticStore.GetPlan(organization.PlanType); await sutProvider.GetDependency().Received(1) - .AdjustSeatsAsync(organization, plan, update.SmSeatsExcludingBase); + .AdjustSmSeatsAsync(organization, plan, update.SmSeatsExcludingBase); await sutProvider.GetDependency().Received(1) .AdjustServiceAccountsAsync(organization, plan, update.SmServiceAccountsExcludingBase); @@ -98,7 +98,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests var plan = StaticStore.GetPlan(organization.PlanType); await sutProvider.GetDependency().Received(1) - .AdjustSeatsAsync(organization, plan, update.SmSeatsExcludingBase); + .AdjustSmSeatsAsync(organization, plan, update.SmSeatsExcludingBase); await sutProvider.GetDependency().Received(1) .AdjustServiceAccountsAsync(organization, plan, update.SmServiceAccountsExcludingBase); @@ -375,6 +375,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests Organization organization, SutProvider sutProvider) { + organization.SmSeats = 8; var update = new SecretsManagerSubscriptionUpdate(organization, false) { SmSeats = 7, @@ -598,7 +599,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests private static async Task VerifyDependencyNotCalledAsync(SutProvider sutProvider) { await sutProvider.GetDependency().DidNotReceive() - .AdjustSeatsAsync(Arg.Any(), Arg.Any(), Arg.Any()); + .AdjustSmSeatsAsync(Arg.Any(), Arg.Any(), Arg.Any()); await sutProvider.GetDependency().DidNotReceive() .AdjustServiceAccountsAsync(Arg.Any(), Arg.Any(), Arg.Any()); // TODO: call ReferenceEventService - see AC-1481 diff --git a/test/Core.Test/Services/EventServiceTests.cs b/test/Core.Test/Services/EventServiceTests.cs index 2cda759b15..ad73541d14 100644 --- a/test/Core.Test/Services/EventServiceTests.cs +++ b/test/Core.Test/Services/EventServiceTests.cs @@ -1,4 +1,5 @@ -using Bit.Core.Context; +using Bit.Core.AdminConsole.Entities; +using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Entities.Provider; using Bit.Core.Enums; diff --git a/test/Core.Test/Vault/AutoFixture/CollectionFixture.cs b/test/Core.Test/Vault/AutoFixture/CollectionFixture.cs index 3a84d3438f..51136acb6d 100644 --- a/test/Core.Test/Vault/AutoFixture/CollectionFixture.cs +++ b/test/Core.Test/Vault/AutoFixture/CollectionFixture.cs @@ -1,4 +1,5 @@ using AutoFixture; +using Bit.Core.AdminConsole.Entities; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Models.Data; diff --git a/test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/GroupCompare.cs b/test/Infrastructure.EFIntegration.Test/AdminConsole/Repositories/EqualityComparers/GroupCompare.cs similarity index 73% rename from test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/GroupCompare.cs rename to test/Infrastructure.EFIntegration.Test/AdminConsole/Repositories/EqualityComparers/GroupCompare.cs index dcb0be2ff1..eb52446af0 100644 --- a/test/Infrastructure.EFIntegration.Test/Repositories/EqualityComparers/GroupCompare.cs +++ b/test/Infrastructure.EFIntegration.Test/AdminConsole/Repositories/EqualityComparers/GroupCompare.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; -namespace Bit.Infrastructure.EFIntegration.Test.Repositories.EqualityComparers; +namespace Bit.Infrastructure.EFIntegration.Test.AdminConsole.Repositories.EqualityComparers; public class GroupCompare : IEqualityComparer { diff --git a/test/Infrastructure.EFIntegration.Test/AutoFixture/GroupFixtures.cs b/test/Infrastructure.EFIntegration.Test/AutoFixture/GroupFixtures.cs index c6cca49015..67d15c1774 100644 --- a/test/Infrastructure.EFIntegration.Test/AutoFixture/GroupFixtures.cs +++ b/test/Infrastructure.EFIntegration.Test/AutoFixture/GroupFixtures.cs @@ -1,7 +1,8 @@ using AutoFixture; using AutoFixture.Kernel; -using Bit.Core.Entities; +using Bit.Core.AdminConsole.Entities; using Bit.Infrastructure.EFIntegration.Test.AutoFixture.Relays; +using Bit.Infrastructure.EntityFramework.AdminConsole.Repositories; using Bit.Infrastructure.EntityFramework.Repositories; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; diff --git a/test/Infrastructure.EFIntegration.Test/AutoFixture/GroupUserFixtures.cs b/test/Infrastructure.EFIntegration.Test/AutoFixture/GroupUserFixtures.cs index 2b68cde322..f127383041 100644 --- a/test/Infrastructure.EFIntegration.Test/AutoFixture/GroupUserFixtures.cs +++ b/test/Infrastructure.EFIntegration.Test/AutoFixture/GroupUserFixtures.cs @@ -1,7 +1,7 @@ using AutoFixture; using AutoFixture.Kernel; -using Bit.Core.Entities; -using Bit.Infrastructure.EntityFramework.Repositories; +using Bit.Core.AdminConsole.Entities; +using Bit.Infrastructure.EntityFramework.AdminConsole.Repositories; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; diff --git a/util/Migrator/DbScripts_finalization/2023-01-FutureMigration.sql b/util/Migrator/DbScripts/2023-09-11_00_2023-01-FutureMigration.sql similarity index 82% rename from util/Migrator/DbScripts_finalization/2023-01-FutureMigration.sql rename to util/Migrator/DbScripts/2023-09-11_00_2023-01-FutureMigration.sql index 9f725c7be2..1f975f42da 100644 --- a/util/Migrator/DbScripts_finalization/2023-01-FutureMigration.sql +++ b/util/Migrator/DbScripts/2023-09-11_00_2023-01-FutureMigration.sql @@ -51,6 +51,8 @@ CREATE OR ALTER PROCEDURE [dbo].[User_Create] @LicenseKey VARCHAR(100), @Kdf TINYINT, @KdfIterations INT, + @KdfMemory INT = NULL, + @KdfParallelism INT = NULL, @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), @ApiKey VARCHAR(30), @@ -58,7 +60,11 @@ CREATE OR ALTER PROCEDURE [dbo].[User_Create] @UsesKeyConnector BIT = 0, @FailedLoginCount INT = 0, @LastFailedLoginDate DATETIME2(7), - @AvatarColor VARCHAR(7) = NULL + @AvatarColor VARCHAR(7) = NULL, + @LastPasswordChangeDate DATETIME2(7) = NULL, + @LastKdfChangeDate DATETIME2(7) = NULL, + @LastKeyRotationDate DATETIME2(7) = NULL, + @LastEmailChangeDate DATETIME2(7) = NULL AS BEGIN SET NOCOUNT ON @@ -100,7 +106,13 @@ BEGIN [UsesKeyConnector], [FailedLoginCount], [LastFailedLoginDate], - [AvatarColor] + [AvatarColor], + [KdfMemory], + [KdfParallelism], + [LastPasswordChangeDate], + [LastKdfChangeDate], + [LastKeyRotationDate], + [LastEmailChangeDate] ) VALUES ( @@ -139,7 +151,13 @@ BEGIN @UsesKeyConnector, @FailedLoginCount, @LastFailedLoginDate, - @AvatarColor + @AvatarColor, + @KdfMemory, + @KdfParallelism, + @LastPasswordChangeDate, + @LastKdfChangeDate, + @LastKeyRotationDate, + @LastEmailChangeDate ) END GO @@ -174,6 +192,8 @@ CREATE OR ALTER PROCEDURE [dbo].[User_Update] @LicenseKey VARCHAR(100), @Kdf TINYINT, @KdfIterations INT, + @KdfMemory INT = NULL, + @KdfParallelism INT = NULL, @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), @ApiKey VARCHAR(30), @@ -181,7 +201,11 @@ CREATE OR ALTER PROCEDURE [dbo].[User_Update] @UsesKeyConnector BIT = 0, @FailedLoginCount INT, @LastFailedLoginDate DATETIME2(7), - @AvatarColor VARCHAR(7) + @AvatarColor VARCHAR(7), + @LastPasswordChangeDate DATETIME2(7) = NULL, + @LastKdfChangeDate DATETIME2(7) = NULL, + @LastKeyRotationDate DATETIME2(7) = NULL, + @LastEmailChangeDate DATETIME2(7) = NULL AS BEGIN SET NOCOUNT ON @@ -216,6 +240,8 @@ BEGIN [LicenseKey] = @LicenseKey, [Kdf] = @Kdf, [KdfIterations] = @KdfIterations, + [KdfMemory] = @KdfMemory, + [KdfParallelism] = @KdfParallelism, [CreationDate] = @CreationDate, [RevisionDate] = @RevisionDate, [ApiKey] = @ApiKey, @@ -223,7 +249,11 @@ BEGIN [UsesKeyConnector] = @UsesKeyConnector, [FailedLoginCount] = @FailedLoginCount, [LastFailedLoginDate] = @LastFailedLoginDate, - [AvatarColor] = @AvatarColor + [AvatarColor] = @AvatarColor, + [LastPasswordChangeDate] = @LastPasswordChangeDate, + [LastKdfChangeDate] = @LastKdfChangeDate, + [LastKeyRotationDate] = @LastKeyRotationDate, + [LastEmailChangeDate] = @LastEmailChangeDate WHERE [Id] = @Id END diff --git a/util/Migrator/DbScripts_finalization/2023-02-FutureMigration.sql b/util/Migrator/DbScripts/2023-09-11_01_2023-02-FutureMigration.sql similarity index 100% rename from util/Migrator/DbScripts_finalization/2023-02-FutureMigration.sql rename to util/Migrator/DbScripts/2023-09-11_01_2023-02-FutureMigration.sql diff --git a/util/Migrator/DbScripts/2023-09-29_00_OrgDomainReadByIdOrgId.sql b/util/Migrator/DbScripts/2023-09-29_00_OrgDomainReadByIdOrgId.sql new file mode 100644 index 0000000000..501bf70103 --- /dev/null +++ b/util/Migrator/DbScripts/2023-09-29_00_OrgDomainReadByIdOrgId.sql @@ -0,0 +1,17 @@ +CREATE OR ALTER PROCEDURE [dbo].[OrganizationDomain_ReadByIdOrganizationId] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + +SELECT + * +FROM + [dbo].[OrganizationDomain] +WHERE + [Id] = @Id + AND + [OrganizationId] = @OrganizationId +END +GO diff --git a/util/Migrator/DbScripts/2023-10-03_00_OrganizationReadOwnerEmailAddresses.sql b/util/Migrator/DbScripts/2023-10-03_00_OrganizationReadOwnerEmailAddresses.sql new file mode 100644 index 0000000000..c88b12af03 --- /dev/null +++ b/util/Migrator/DbScripts/2023-10-03_00_OrganizationReadOwnerEmailAddresses.sql @@ -0,0 +1,17 @@ +CREATE OR ALTER PROCEDURE [dbo].[Organization_ReadOwnerEmailAddressesById] + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + [U].[Email] + FROM [User] AS [U] + INNER JOIN [OrganizationUser] AS [OU] ON [U].[Id] = [OU].[UserId] + WHERE + [OU].[OrganizationId] = @OrganizationId AND + [OU].[Type] = 0 AND -- Owner + [OU].[Status] = 2 -- Confirmed + GROUP BY [U].[Email] +END +GO diff --git a/util/Migrator/DbScripts/2023-10-05_00_OrgConnectionsReadByIdOrgId.sql b/util/Migrator/DbScripts/2023-10-05_00_OrgConnectionsReadByIdOrgId.sql new file mode 100644 index 0000000000..34c9503086 --- /dev/null +++ b/util/Migrator/DbScripts/2023-10-05_00_OrgConnectionsReadByIdOrgId.sql @@ -0,0 +1,16 @@ +CREATE OR ALTER PROCEDURE [dbo].[OrganizationConnection_ReadByIdOrganizationId] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + +SELECT + * +FROM + [dbo].[OrganizationConnectionView] +WHERE + [Id] = @Id AND + [OrganizationId] = @OrganizationId +END +GO diff --git a/util/Migrator/DbScripts/2023-10-09_00_Event_ReadPageByOrganizationIdServiceAccountId.sql b/util/Migrator/DbScripts/2023-10-09_00_Event_ReadPageByOrganizationIdServiceAccountId.sql new file mode 100644 index 0000000000..334ebc6cad --- /dev/null +++ b/util/Migrator/DbScripts/2023-10-09_00_Event_ReadPageByOrganizationIdServiceAccountId.sql @@ -0,0 +1,25 @@ +CREATE OR ALTER PROCEDURE [dbo].[Event_ReadPageByOrganizationIdServiceAccountId] + @OrganizationId UNIQUEIDENTIFIER, + @ServiceAccountId UNIQUEIDENTIFIER, + @StartDate DATETIME2(7), + @EndDate DATETIME2(7), + @BeforeDate DATETIME2(7), + @PageSize INT +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[EventView] + WHERE + [Date] >= @StartDate + AND (@BeforeDate IS NOT NULL OR [Date] <= @EndDate) + AND (@BeforeDate IS NULL OR [Date] < @BeforeDate) + AND [OrganizationId] = @OrganizationId + AND [ServiceAccountId] = @ServiceAccountId + ORDER BY [Date] DESC + OFFSET 0 ROWS + FETCH NEXT @PageSize ROWS ONLY +END diff --git a/util/Migrator/DbScripts/2023-09-26_00_LimitCollectionCreationDeletion.sql b/util/Migrator/DbScripts/2023-10-24_00_LimitCollectionCreationDeletion.sql similarity index 97% rename from util/Migrator/DbScripts/2023-09-26_00_LimitCollectionCreationDeletion.sql rename to util/Migrator/DbScripts/2023-10-24_00_LimitCollectionCreationDeletion.sql index 6e15f9c36e..735ca36fa6 100644 --- a/util/Migrator/DbScripts/2023-09-26_00_LimitCollectionCreationDeletion.sql +++ b/util/Migrator/DbScripts/2023-10-24_00_LimitCollectionCreationDeletion.sql @@ -1,3 +1,13 @@ +--Dev cleanup: drop previous column name (never used in production but may be present on some QA instances) +IF COL_LENGTH('[dbo].[Organization]', 'LimitCollectionCdOwnerAdmin') IS NOT NULL +BEGIN + ALTER TABLE + [dbo].[Organization] + DROP COLUMN + [LimitCollectionCdOwnerAdmin] +END +GO + --Add column 'LimitCollectionCreationDeletion' to 'Organization' table IF COL_LENGTH('[dbo].[Organization]', 'LimitCollectionCreationDeletion') IS NULL BEGIN @@ -67,7 +77,7 @@ CREATE OR ALTER PROCEDURE [dbo].[Organization_Create] @MaxAutoscaleSmSeats INT= null, @MaxAutoscaleSmServiceAccounts INT = null, @SecretsManagerBeta BIT = 0, - @LimitCollectionCreationDeletion BIT = 0 + @LimitCollectionCreationDeletion BIT = 1 AS BEGIN SET NOCOUNT ON diff --git a/util/Migrator/DbScripts/2023-10-24_01_CollectionManagePermission.sql b/util/Migrator/DbScripts/2023-10-24_01_CollectionManagePermission.sql new file mode 100644 index 0000000000..7341e70976 --- /dev/null +++ b/util/Migrator/DbScripts/2023-10-24_01_CollectionManagePermission.sql @@ -0,0 +1,340 @@ +/* + * Update existing write procedures to safely ignore any newly added columns to the CollectionUser and + * CollectionGroup tables (e.g. preparation for [Manage] in the next migration script). This is accomplished by + * explicitly listing the columns in the INSERT and UPDATE statements. + */ + +-- Update INSERT statement to include explicit column list +CREATE OR ALTER PROCEDURE [dbo].[CollectionUser_UpdateUsers] + @CollectionId UNIQUEIDENTIFIER, + @Users AS [dbo].[SelectionReadOnlyArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + DECLARE @OrgId UNIQUEIDENTIFIER = ( + SELECT TOP 1 + [OrganizationId] + FROM + [dbo].[Collection] + WHERE + [Id] = @CollectionId + ) + + -- Update + UPDATE + [Target] + SET + [Target].[ReadOnly] = [Source].[ReadOnly], + [Target].[HidePasswords] = [Source].[HidePasswords] + FROM + [dbo].[CollectionUser] [Target] + INNER JOIN + @Users [Source] ON [Source].[Id] = [Target].[OrganizationUserId] + WHERE + [Target].[CollectionId] = @CollectionId + AND ( + [Target].[ReadOnly] != [Source].[ReadOnly] + OR [Target].[HidePasswords] != [Source].[HidePasswords] + ) + + -- Insert + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords] + ) + SELECT + @CollectionId, + [Source].[Id], + [Source].[ReadOnly], + [Source].[HidePasswords] + FROM + @Users [Source] + INNER JOIN + [dbo].[OrganizationUser] OU ON [Source].[Id] = OU.[Id] AND OU.[OrganizationId] = @OrgId + WHERE + NOT EXISTS ( + SELECT + 1 + FROM + [dbo].[CollectionUser] + WHERE + [CollectionId] = @CollectionId + AND [OrganizationUserId] = [Source].[Id] + ) + + -- Delete + DELETE + CU + FROM + [dbo].[CollectionUser] CU + WHERE + CU.[CollectionId] = @CollectionId + AND NOT EXISTS ( + SELECT + 1 + FROM + @Users + WHERE + [Id] = CU.[OrganizationUserId] + ) + + EXEC [dbo].[User_BumpAccountRevisionDateByCollectionId] @CollectionId, @OrgId +END +GO + +-- Update INSERT statement to include explicit column list +CREATE OR ALTER PROCEDURE [dbo].[Group_UpdateWithCollections] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Name NVARCHAR(100), + @AccessAll BIT, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Collections AS [dbo].[SelectionReadOnlyArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[Group_Update] @Id, @OrganizationId, @Name, @AccessAll, @ExternalId, @CreationDate, @RevisionDate + + ;WITH [AvailableCollectionsCTE] AS( + SELECT + Id + FROM + [dbo].[Collection] + WHERE + OrganizationId = @OrganizationId + ) + MERGE + [dbo].[CollectionGroup] AS [Target] + USING + @Collections AS [Source] + ON + [Target].[CollectionId] = [Source].[Id] + AND [Target].[GroupId] = @Id + WHEN NOT MATCHED BY TARGET + AND [Source].[Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN + INSERT + ( + [CollectionId], + [GroupId], + [ReadOnly], + [HidePasswords] + ) + VALUES + ( + [Source].[Id], + @Id, + [Source].[ReadOnly], + [Source].[HidePasswords] + ) + WHEN MATCHED AND ( + [Target].[ReadOnly] != [Source].[ReadOnly] + OR [Target].[HidePasswords] != [Source].[HidePasswords] + ) THEN + UPDATE SET [Target].[ReadOnly] = [Source].[ReadOnly], + [Target].[HidePasswords] = [Source].[HidePasswords] + WHEN NOT MATCHED BY SOURCE + AND [Target].[GroupId] = @Id THEN + DELETE + ; + + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId +END +GO + +-- Update INSERT statements to include explicit column list +CREATE OR ALTER PROCEDURE [dbo].[Collection_UpdateWithGroupsAndUsers] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Name VARCHAR(MAX), + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Groups AS [dbo].[SelectionReadOnlyArray] READONLY, + @Users AS [dbo].[SelectionReadOnlyArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[Collection_Update] @Id, @OrganizationId, @Name, @ExternalId, @CreationDate, @RevisionDate + + -- Groups + ;WITH [AvailableGroupsCTE] AS( + SELECT + Id + FROM + [dbo].[Group] + WHERE + OrganizationId = @OrganizationId + ) + MERGE + [dbo].[CollectionGroup] AS [Target] + USING + @Groups AS [Source] + ON + [Target].[CollectionId] = @Id + AND [Target].[GroupId] = [Source].[Id] + WHEN NOT MATCHED BY TARGET + AND [Source].[Id] IN (SELECT [Id] FROM [AvailableGroupsCTE]) THEN + INSERT -- With column list because a value for Manage is not being provided + ( + [CollectionId], + [GroupId], + [ReadOnly], + [HidePasswords] + ) + VALUES + ( + @Id, + [Source].[Id], + [Source].[ReadOnly], + [Source].[HidePasswords] + ) + WHEN MATCHED AND ( + [Target].[ReadOnly] != [Source].[ReadOnly] + OR [Target].[HidePasswords] != [Source].[HidePasswords] + ) THEN + UPDATE SET [Target].[ReadOnly] = [Source].[ReadOnly], + [Target].[HidePasswords] = [Source].[HidePasswords] + WHEN NOT MATCHED BY SOURCE + AND [Target].[CollectionId] = @Id THEN + DELETE + ; + + -- Users + ;WITH [AvailableGroupsCTE] AS( + SELECT + Id + FROM + [dbo].[OrganizationUser] + WHERE + OrganizationId = @OrganizationId + ) + MERGE + [dbo].[CollectionUser] AS [Target] + USING + @Users AS [Source] + ON + [Target].[CollectionId] = @Id + AND [Target].[OrganizationUserId] = [Source].[Id] + WHEN NOT MATCHED BY TARGET + AND [Source].[Id] IN (SELECT [Id] FROM [AvailableGroupsCTE]) THEN + INSERT + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords] + ) + VALUES + ( + @Id, + [Source].[Id], + [Source].[ReadOnly], + [Source].[HidePasswords] + ) + WHEN MATCHED AND ( + [Target].[ReadOnly] != [Source].[ReadOnly] + OR [Target].[HidePasswords] != [Source].[HidePasswords] + ) THEN + UPDATE SET [Target].[ReadOnly] = [Source].[ReadOnly], + [Target].[HidePasswords] = [Source].[HidePasswords] + WHEN NOT MATCHED BY SOURCE + AND [Target].[CollectionId] = @Id THEN + DELETE + ; + + EXEC [dbo].[User_BumpAccountRevisionDateByCollectionId] @Id, @OrganizationId +END +GO + +-- Update INSERT statement to include explicit column list +CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_UpdateWithCollections] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @Email NVARCHAR(256), + @Key VARCHAR(MAX), + @Status SMALLINT, + @Type TINYINT, + @AccessAll BIT, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Permissions NVARCHAR(MAX), + @ResetPasswordKey VARCHAR(MAX), + @Collections AS [dbo].[SelectionReadOnlyArray] READONLY, + @AccessSecretsManager BIT = 0 +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[OrganizationUser_Update] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate, @Permissions, @ResetPasswordKey, @AccessSecretsManager + -- Update + UPDATE + [Target] + SET + [Target].[ReadOnly] = [Source].[ReadOnly], + [Target].[HidePasswords] = [Source].[HidePasswords] + FROM + [dbo].[CollectionUser] AS [Target] + INNER JOIN + @Collections AS [Source] ON [Source].[Id] = [Target].[CollectionId] + WHERE + [Target].[OrganizationUserId] = @Id + AND ( + [Target].[ReadOnly] != [Source].[ReadOnly] + OR [Target].[HidePasswords] != [Source].[HidePasswords] + ) + + -- Insert + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords] + ) + SELECT + [Source].[Id], + @Id, + [Source].[ReadOnly], + [Source].[HidePasswords] + FROM + @Collections AS [Source] + INNER JOIN + [dbo].[Collection] C ON C.[Id] = [Source].[Id] AND C.[OrganizationId] = @OrganizationId + WHERE + NOT EXISTS ( + SELECT + 1 + FROM + [dbo].[CollectionUser] + WHERE + [CollectionId] = [Source].[Id] + AND [OrganizationUserId] = @Id + ) + + -- Delete + DELETE + CU + FROM + [dbo].[CollectionUser] CU + WHERE + CU.[OrganizationUserId] = @Id + AND NOT EXISTS ( + SELECT + 1 + FROM + @Collections + WHERE + [Id] = CU.[CollectionId] + ) +END +GO diff --git a/util/Migrator/DbScripts/2023-07-11_00_CollectionManagePermission.sql b/util/Migrator/DbScripts/2023-10-24_02_CollectionManagePermission.sql similarity index 78% rename from util/Migrator/DbScripts/2023-07-11_00_CollectionManagePermission.sql rename to util/Migrator/DbScripts/2023-10-24_02_CollectionManagePermission.sql index 50e4c7d96c..2d6f45bf3f 100644 --- a/util/Migrator/DbScripts/2023-07-11_00_CollectionManagePermission.sql +++ b/util/Migrator/DbScripts/2023-10-24_02_CollectionManagePermission.sql @@ -1,79 +1,83 @@ /* - * Add Manage permission to collections + * Add Manage permission to collections and update associated stored procedures */ --- Drop procedures that use the SelectionReadOnlyArray type -IF OBJECT_ID('[dbo].[Group_CreateWithCollections]') IS NOT NULL +-- To allow the migration to be re-run, drop any of the V2 procedures as they depend on a new type + +IF OBJECT_ID('[dbo].[CollectionUser_UpdateUsers_V2]') IS NOT NULL BEGIN - DROP PROCEDURE [dbo].[Group_CreateWithCollections] + DROP PROCEDURE [dbo].[CollectionUser_UpdateUsers_V2] END GO -IF OBJECT_ID('[dbo].[CollectionUser_UpdateUsers]') IS NOT NULL +IF OBJECT_ID('[dbo].[Group_UpdateWithCollections_V2]') IS NOT NULL BEGIN - DROP PROCEDURE [dbo].[CollectionUser_UpdateUsers] + DROP PROCEDURE [dbo].[Group_UpdateWithCollections_V2] END GO -IF OBJECT_ID('[dbo].[Group_UpdateWithCollections]') IS NOT NULL +IF OBJECT_ID('[dbo].[Collection_UpdateWithGroupsAndUsers_V2]') IS NOT NULL BEGIN - DROP PROCEDURE [dbo].[Group_UpdateWithCollections] + DROP PROCEDURE [dbo].[Collection_UpdateWithGroupsAndUsers_V2] END GO -IF OBJECT_ID('[dbo].[Collection_UpdateWithGroupsAndUsers]') IS NOT NULL +IF OBJECT_ID('[dbo].[OrganizationUser_UpdateWithCollections_V2]') IS NOT NULL BEGIN - DROP PROCEDURE [dbo].[Collection_UpdateWithGroupsAndUsers] + DROP PROCEDURE [dbo].[OrganizationUser_UpdateWithCollections_V2] END GO -IF OBJECT_ID('[dbo].[OrganizationUser_CreateWithCollections]') IS NOT NULL +IF OBJECT_ID('[dbo].[Group_CreateWithCollections_V2]') IS NOT NULL BEGIN - DROP PROCEDURE [dbo].[OrganizationUser_CreateWithCollections] + DROP PROCEDURE [dbo].[Group_CreateWithCollections_V2] END GO -IF OBJECT_ID('[dbo].[OrganizationUser_UpdateWithCollections]') IS NOT NULL +IF OBJECT_ID('[dbo].[OrganizationUser_CreateWithCollections_V2]') IS NOT NULL BEGIN - DROP PROCEDURE [dbo].[OrganizationUser_UpdateWithCollections] + DROP PROCEDURE [dbo].[OrganizationUser_CreateWithCollections_V2] END GO -IF OBJECT_ID('[dbo].[Collection_CreateWithGroupsAndUsers]') IS NOT NULL +IF OBJECT_ID('[dbo].[Collection_CreateWithGroupsAndUsers_V2]') IS NOT NULL BEGIN - DROP PROCEDURE [dbo].[Collection_CreateWithGroupsAndUsers] + DROP PROCEDURE [dbo].[Collection_CreateWithGroupsAndUsers_V2] END GO -IF TYPE_ID('[dbo].[SelectionReadOnlyArray]') IS NOT NULL +-- Create a new CollectionAccessSelectionType with a new [Manage] column +IF TYPE_ID('[dbo].[CollectionAccessSelectionType]') IS NOT NULL BEGIN - DROP TYPE [dbo].[SelectionReadOnlyArray] + DROP TYPE [dbo].[CollectionAccessSelectionType] END GO -CREATE TYPE [dbo].[SelectionReadOnlyArray] AS TABLE ( +CREATE TYPE [dbo].[CollectionAccessSelectionType] AS TABLE ( [Id] UNIQUEIDENTIFIER NOT NULL, [ReadOnly] BIT NOT NULL, [HidePasswords] BIT NOT NULL, [Manage] BIT NOT NULL); GO - - ---Add Manage Column +-- Add Manage Column IF COL_LENGTH('[dbo].[CollectionUser]', 'Manage') IS NULL BEGIN ALTER TABLE [dbo].[CollectionUser] ADD [Manage] BIT NOT NULL CONSTRAINT D_CollectionUser_Manage DEFAULT (0); END GO ---Add Manage Column +-- Add Manage Column IF COL_LENGTH('[dbo].[CollectionGroup]', 'Manage') IS NULL BEGIN ALTER TABLE [dbo].[CollectionGroup] ADD [Manage] BIT NOT NULL CONSTRAINT D_CollectionGroup_Manage DEFAULT (0); END GO +-- BEGIN Update procedures that support backwards compatability in place +-- These procedures can be safely used by server in case of rollback and do not require V2 versions + +-- Readonly query that adds [Manage] column to result, safely ignored by rolled back server CREATE OR ALTER PROCEDURE [dbo].[CollectionUser_ReadByCollectionId] @CollectionId UNIQUEIDENTIFIER AS @@ -92,6 +96,7 @@ BEGIN END GO +-- Readonly query that adds [Manage] column to result, safely ignored by rolled back server CREATE OR ALTER PROCEDURE [dbo].[CollectionGroup_ReadByCollectionId] @CollectionId UNIQUEIDENTIFIER AS @@ -110,55 +115,215 @@ BEGIN END GO -CREATE PROCEDURE [dbo].[Group_CreateWithCollections] - @Id UNIQUEIDENTIFIER, - @OrganizationId UNIQUEIDENTIFIER, - @Name NVARCHAR(100), - @AccessAll BIT, - @ExternalId NVARCHAR(300), - @CreationDate DATETIME2(7), - @RevisionDate DATETIME2(7), - @Collections AS [dbo].[SelectionReadOnlyArray] READONLY +-- Readonly query that adds [Manage] column to result, safely ignored by rolled back server +CREATE OR ALTER PROCEDURE [dbo].[OrganizationUserUserDetails_ReadWithCollectionsById] + @Id UNIQUEIDENTIFIER AS BEGIN SET NOCOUNT ON - EXEC [dbo].[Group_Create] @Id, @OrganizationId, @Name, @AccessAll, @ExternalId, @CreationDate, @RevisionDate + EXEC [OrganizationUserUserDetails_ReadById] @Id - ;WITH [AvailableCollectionsCTE] AS( - SELECT - [Id] - FROM - [dbo].[Collection] - WHERE - [OrganizationId] = @OrganizationId - ) - INSERT INTO [dbo].[CollectionGroup] - ( - [CollectionId], - [GroupId], - [ReadOnly], - [HidePasswords], - [Manage] +SELECT + CU.[CollectionId] Id, + CU.[ReadOnly], + CU.[HidePasswords], + CU.[Manage] +FROM + [dbo].[OrganizationUser] OU + INNER JOIN + [dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[OrganizationUserId] = [OU].[Id] +WHERE + [OrganizationUserId] = @Id +END +GO + +-- Readonly function that adds [Manage] column to result, safely ignored by rolled back server +CREATE OR ALTER FUNCTION [dbo].[UserCollectionDetails](@UserId UNIQUEIDENTIFIER) +RETURNS TABLE +AS RETURN +SELECT + C.*, + CASE + WHEN + OU.[AccessAll] = 1 + OR G.[AccessAll] = 1 + OR COALESCE(CU.[ReadOnly], CG.[ReadOnly], 0) = 0 + THEN 0 + ELSE 1 + END [ReadOnly], + CASE + WHEN + OU.[AccessAll] = 1 + OR G.[AccessAll] = 1 + OR COALESCE(CU.[HidePasswords], CG.[HidePasswords], 0) = 0 + THEN 0 + ELSE 1 + END [HidePasswords], + CASE + WHEN + OU.[AccessAll] = 1 + OR G.[AccessAll] = 1 + OR COALESCE(CU.[Manage], CG.[Manage], 0) = 0 + THEN 0 + ELSE 1 + END [Manage] +FROM + [dbo].[CollectionView] C +INNER JOIN + [dbo].[OrganizationUser] OU ON C.[OrganizationId] = OU.[OrganizationId] +INNER JOIN + [dbo].[Organization] O ON O.[Id] = C.[OrganizationId] +LEFT JOIN + [dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = [OU].[Id] +LEFT JOIN + [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] +LEFT JOIN + [dbo].[Group] G ON G.[Id] = GU.[GroupId] +LEFT JOIN + [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] +WHERE + OU.[UserId] = @UserId + AND OU.[Status] = 2 -- 2 = Confirmed + AND O.[Enabled] = 1 + AND ( + OU.[AccessAll] = 1 + OR CU.[CollectionId] IS NOT NULL + OR G.[AccessAll] = 1 + OR CG.[CollectionId] IS NOT NULL ) +GO + +-- Readonly query that adds [Manage] column to result, safely ignored by rolled back server +CREATE OR ALTER PROCEDURE [dbo].[Collection_ReadByIdUserId] + @Id UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON SELECT - [Id], - @Id, + Id, + OrganizationId, + [Name], + CreationDate, + RevisionDate, + ExternalId, + MIN([ReadOnly]) AS [ReadOnly], + MIN([HidePasswords]) AS [HidePasswords], + MIN([Manage]) AS [Manage] + FROM + [dbo].[UserCollectionDetails](@UserId) + WHERE + [Id] = @Id + GROUP BY + Id, + OrganizationId, + [Name], + CreationDate, + RevisionDate, + ExternalId +END +GO + +-- Readonly query that adds [Manage] column to result, safely ignored by rolled back server +CREATE OR ALTER PROCEDURE [dbo].[Collection_ReadByUserId] + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + Id, + OrganizationId, + [Name], + CreationDate, + RevisionDate, + ExternalId, + MIN([ReadOnly]) AS [ReadOnly], + MIN([HidePasswords]) AS [HidePasswords], + MIN([Manage]) AS [Manage] + FROM + [dbo].[UserCollectionDetails](@UserId) + GROUP BY + Id, + OrganizationId, + [Name], + CreationDate, + RevisionDate, + ExternalId +END +GO + +-- Readonly query that adds [Manage] column to result, safely ignored by rolled back server +CREATE OR ALTER PROCEDURE [dbo].[Collection_ReadWithGroupsAndUsersByUserId] + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + DECLARE @TempUserCollections TABLE( + Id UNIQUEIDENTIFIER, + OrganizationId UNIQUEIDENTIFIER, + Name VARCHAR(MAX), + CreationDate DATETIME2(7), + RevisionDate DATETIME2(7), + ExternalId NVARCHAR(300), + ReadOnly BIT, + HidePasswords BIT, + Manage BIT) + + INSERT INTO @TempUserCollections EXEC [dbo].[Collection_ReadByUserId] @UserId + + SELECT + * + FROM + @TempUserCollections C + + SELECT + CG.* + FROM + [dbo].[CollectionGroup] CG + INNER JOIN + @TempUserCollections C ON C.[Id] = CG.[CollectionId] + + SELECT + CU.* + FROM + [dbo].[CollectionUser] CU + INNER JOIN + @TempUserCollections C ON C.[Id] = CU.[CollectionId] + +END +GO + +-- Readonly query that adds [Manage] column to result, safely ignored by rolled back server +CREATE OR ALTER PROCEDURE [dbo].[Group_ReadWithCollectionsById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[Group_ReadById] @Id + + SELECT + [CollectionId] [Id], [ReadOnly], [HidePasswords], [Manage] FROM - @Collections + [dbo].[CollectionGroup] WHERE - [Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) - - EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId + [GroupId] = @Id END GO -CREATE PROCEDURE [dbo].[CollectionUser_UpdateUsers] +-- END Update procedures that support backwards compatability in place + +-- BEGIN Create V2 of existing procedures to support new [Manage] column and new CollectionAccessSelectionType + +CREATE OR ALTER PROCEDURE [dbo].[CollectionUser_UpdateUsers_V2] @CollectionId UNIQUEIDENTIFIER, - @Users AS [dbo].[SelectionReadOnlyArray] READONLY + @Users AS [dbo].[CollectionAccessSelectionType] READONLY AS BEGIN SET NOCOUNT ON @@ -192,8 +357,14 @@ BEGIN ) -- Insert - INSERT INTO - [dbo].[CollectionUser] + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) SELECT @CollectionId, [Source].[Id], @@ -214,7 +385,7 @@ BEGIN [CollectionId] = @CollectionId AND [OrganizationUserId] = [Source].[Id] ) - + -- Delete DELETE CU @@ -235,7 +406,7 @@ BEGIN END GO -CREATE PROCEDURE [dbo].[Group_UpdateWithCollections] +CREATE OR ALTER PROCEDURE [dbo].[Group_UpdateWithCollections_V2] @Id UNIQUEIDENTIFIER, @OrganizationId UNIQUEIDENTIFIER, @Name NVARCHAR(100), @@ -243,7 +414,7 @@ CREATE PROCEDURE [dbo].[Group_UpdateWithCollections] @ExternalId NVARCHAR(300), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), - @Collections AS [dbo].[SelectionReadOnlyArray] READONLY + @Collections AS [dbo].[CollectionAccessSelectionType] READONLY AS BEGIN SET NOCOUNT ON @@ -260,14 +431,22 @@ BEGIN ) MERGE [dbo].[CollectionGroup] AS [Target] - USING + USING @Collections AS [Source] ON [Target].[CollectionId] = [Source].[Id] AND [Target].[GroupId] = @Id WHEN NOT MATCHED BY TARGET AND [Source].[Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN - INSERT VALUES + INSERT + ( + [CollectionId], + [GroupId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + VALUES ( [Source].[Id], @Id, @@ -292,15 +471,15 @@ BEGIN END GO -CREATE PROCEDURE [dbo].[Collection_UpdateWithGroupsAndUsers] +CREATE OR ALTER PROCEDURE [dbo].[Collection_UpdateWithGroupsAndUsers_V2] @Id UNIQUEIDENTIFIER, @OrganizationId UNIQUEIDENTIFIER, @Name VARCHAR(MAX), @ExternalId NVARCHAR(300), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), - @Groups AS [dbo].[SelectionReadOnlyArray] READONLY, - @Users AS [dbo].[SelectionReadOnlyArray] READONLY + @Groups AS [dbo].[CollectionAccessSelectionType] READONLY, + @Users AS [dbo].[CollectionAccessSelectionType] READONLY AS BEGIN SET NOCOUNT ON @@ -318,14 +497,22 @@ BEGIN ) MERGE [dbo].[CollectionGroup] AS [Target] - USING + USING @Groups AS [Source] ON [Target].[CollectionId] = @Id AND [Target].[GroupId] = [Source].[Id] WHEN NOT MATCHED BY TARGET AND [Source].[Id] IN (SELECT [Id] FROM [AvailableGroupsCTE]) THEN - INSERT VALUES + INSERT -- Add explicit column list + ( + [CollectionId], + [GroupId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + VALUES ( @Id, [Source].[Id], @@ -357,14 +544,22 @@ BEGIN ) MERGE [dbo].[CollectionUser] AS [Target] - USING + USING @Users AS [Source] ON [Target].[CollectionId] = @Id AND [Target].[OrganizationUserId] = [Source].[Id] WHEN NOT MATCHED BY TARGET AND [Source].[Id] IN (SELECT [Id] FROM [AvailableGroupsCTE]) THEN - INSERT VALUES + INSERT + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + VALUES ( @Id, [Source].[Id], @@ -389,7 +584,7 @@ BEGIN END GO -CREATE PROCEDURE [dbo].[OrganizationUser_CreateWithCollections] +CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_UpdateWithCollections_V2] @Id UNIQUEIDENTIFIER, @OrganizationId UNIQUEIDENTIFIER, @UserId UNIQUEIDENTIFIER, @@ -403,58 +598,7 @@ CREATE PROCEDURE [dbo].[OrganizationUser_CreateWithCollections] @RevisionDate DATETIME2(7), @Permissions NVARCHAR(MAX), @ResetPasswordKey VARCHAR(MAX), - @Collections AS [dbo].[SelectionReadOnlyArray] READONLY, - @AccessSecretsManager BIT = 0 -AS -BEGIN - SET NOCOUNT ON - - EXEC [dbo].[OrganizationUser_Create] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate, @Permissions, @ResetPasswordKey, @AccessSecretsManager - - ;WITH [AvailableCollectionsCTE] AS( - SELECT - [Id] - FROM - [dbo].[Collection] - WHERE - [OrganizationId] = @OrganizationId - ) - INSERT INTO [dbo].[CollectionUser] - ( - [CollectionId], - [OrganizationUserId], - [ReadOnly], - [HidePasswords], - [Manage] - ) - SELECT - [Id], - @Id, - [ReadOnly], - [HidePasswords], - [Manage] - FROM - @Collections - WHERE - [Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) -END -GO - -CREATE PROCEDURE [dbo].[OrganizationUser_UpdateWithCollections] - @Id UNIQUEIDENTIFIER, - @OrganizationId UNIQUEIDENTIFIER, - @UserId UNIQUEIDENTIFIER, - @Email NVARCHAR(256), - @Key VARCHAR(MAX), - @Status SMALLINT, - @Type TINYINT, - @AccessAll BIT, - @ExternalId NVARCHAR(300), - @CreationDate DATETIME2(7), - @RevisionDate DATETIME2(7), - @Permissions NVARCHAR(MAX), - @ResetPasswordKey VARCHAR(MAX), - @Collections AS [dbo].[SelectionReadOnlyArray] READONLY, + @Collections AS [dbo].[CollectionAccessSelectionType] READONLY, @AccessSecretsManager BIT = 0 AS BEGIN @@ -481,8 +625,14 @@ BEGIN ) -- Insert - INSERT INTO - [dbo].[CollectionUser] + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) SELECT [Source].[Id], @Id, @@ -522,15 +672,112 @@ BEGIN END GO -CREATE PROCEDURE [dbo].[Collection_CreateWithGroupsAndUsers] +CREATE OR ALTER PROCEDURE [dbo].[Group_CreateWithCollections_V2] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Name NVARCHAR(100), + @AccessAll BIT, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Collections AS [dbo].[CollectionAccessSelectionType] READONLY +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[Group_Create] @Id, @OrganizationId, @Name, @AccessAll, @ExternalId, @CreationDate, @RevisionDate + + ;WITH [AvailableCollectionsCTE] AS( + SELECT + [Id] + FROM + [dbo].[Collection] + WHERE + [OrganizationId] = @OrganizationId + ) + INSERT INTO [dbo].[CollectionGroup] + ( + [CollectionId], + [GroupId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + SELECT + [Id], + @Id, + [ReadOnly], + [HidePasswords], + [Manage] + FROM + @Collections + WHERE + [Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) + + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_CreateWithCollections_V2] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @UserId UNIQUEIDENTIFIER, + @Email NVARCHAR(256), + @Key VARCHAR(MAX), + @Status SMALLINT, + @Type TINYINT, + @AccessAll BIT, + @ExternalId NVARCHAR(300), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @Permissions NVARCHAR(MAX), + @ResetPasswordKey VARCHAR(MAX), + @Collections AS [dbo].[CollectionAccessSelectionType] READONLY, + @AccessSecretsManager BIT = 0 +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[OrganizationUser_Create] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate, @Permissions, @ResetPasswordKey, @AccessSecretsManager + + ;WITH [AvailableCollectionsCTE] AS( + SELECT + [Id] + FROM + [dbo].[Collection] + WHERE + [OrganizationId] = @OrganizationId + ) + INSERT INTO [dbo].[CollectionUser] + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + SELECT + [Id], + @Id, + [ReadOnly], + [HidePasswords], + [Manage] + FROM + @Collections + WHERE + [Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) +END +GO + +CREATE OR ALTER PROCEDURE [dbo].[Collection_CreateWithGroupsAndUsers_V2] @Id UNIQUEIDENTIFIER, @OrganizationId UNIQUEIDENTIFIER, @Name VARCHAR(MAX), @ExternalId NVARCHAR(300), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), - @Groups AS [dbo].[SelectionReadOnlyArray] READONLY, - @Users AS [dbo].[SelectionReadOnlyArray] READONLY + @Groups AS [dbo].[CollectionAccessSelectionType] READONLY, + @Users AS [dbo].[CollectionAccessSelectionType] READONLY AS BEGIN SET NOCOUNT ON @@ -596,199 +843,3 @@ BEGIN EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId END GO - -CREATE OR ALTER PROCEDURE [dbo].[OrganizationUserUserDetails_ReadWithCollectionsById] - @Id UNIQUEIDENTIFIER -AS -BEGIN - SET NOCOUNT ON - - EXEC [OrganizationUserUserDetails_ReadById] @Id - - SELECT - CU.[CollectionId] Id, - CU.[ReadOnly], - CU.[HidePasswords], - CU.[Manage] - FROM - [dbo].[OrganizationUser] OU - INNER JOIN - [dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[OrganizationUserId] = [OU].[Id] - WHERE - [OrganizationUserId] = @Id -END -GO - -CREATE OR ALTER FUNCTION [dbo].[UserCollectionDetails](@UserId UNIQUEIDENTIFIER) -RETURNS TABLE -AS RETURN -SELECT - C.*, - CASE - WHEN - OU.[AccessAll] = 1 - OR G.[AccessAll] = 1 - OR COALESCE(CU.[ReadOnly], CG.[ReadOnly], 0) = 0 - THEN 0 - ELSE 1 - END [ReadOnly], - CASE - WHEN - OU.[AccessAll] = 1 - OR G.[AccessAll] = 1 - OR COALESCE(CU.[HidePasswords], CG.[HidePasswords], 0) = 0 - THEN 0 - ELSE 1 - END [HidePasswords], - CASE - WHEN - OU.[AccessAll] = 1 - OR G.[AccessAll] = 1 - OR COALESCE(CU.[Manage], CG.[Manage], 0) = 0 - THEN 0 - ELSE 1 - END [Manage] -FROM - [dbo].[CollectionView] C -INNER JOIN - [dbo].[OrganizationUser] OU ON C.[OrganizationId] = OU.[OrganizationId] -INNER JOIN - [dbo].[Organization] O ON O.[Id] = C.[OrganizationId] -LEFT JOIN - [dbo].[CollectionUser] CU ON OU.[AccessAll] = 0 AND CU.[CollectionId] = C.[Id] AND CU.[OrganizationUserId] = [OU].[Id] -LEFT JOIN - [dbo].[GroupUser] GU ON CU.[CollectionId] IS NULL AND OU.[AccessAll] = 0 AND GU.[OrganizationUserId] = OU.[Id] -LEFT JOIN - [dbo].[Group] G ON G.[Id] = GU.[GroupId] -LEFT JOIN - [dbo].[CollectionGroup] CG ON G.[AccessAll] = 0 AND CG.[CollectionId] = C.[Id] AND CG.[GroupId] = GU.[GroupId] -WHERE - OU.[UserId] = @UserId - AND OU.[Status] = 2 -- 2 = Confirmed - AND O.[Enabled] = 1 - AND ( - OU.[AccessAll] = 1 - OR CU.[CollectionId] IS NOT NULL - OR G.[AccessAll] = 1 - OR CG.[CollectionId] IS NOT NULL - ) -GO - -CREATE OR ALTER PROCEDURE [dbo].[Collection_ReadByIdUserId] - @Id UNIQUEIDENTIFIER, - @UserId UNIQUEIDENTIFIER -AS -BEGIN - SET NOCOUNT ON - SELECT - Id, - OrganizationId, - [Name], - CreationDate, - RevisionDate, - ExternalId, - MIN([ReadOnly]) AS [ReadOnly], - MIN([HidePasswords]) AS [HidePasswords], - MIN([Manage]) AS [Manage] - FROM - [dbo].[UserCollectionDetails](@UserId) - WHERE - [Id] = @Id - GROUP BY - Id, - OrganizationId, - [Name], - CreationDate, - RevisionDate, - ExternalId -END -GO - -CREATE OR ALTER PROCEDURE [dbo].[Collection_ReadByUserId] - @UserId UNIQUEIDENTIFIER -AS -BEGIN - SET NOCOUNT ON - - SELECT - Id, - OrganizationId, - [Name], - CreationDate, - RevisionDate, - ExternalId, - MIN([ReadOnly]) AS [ReadOnly], - MIN([HidePasswords]) AS [HidePasswords], - MIN([Manage]) AS [Manage] - FROM - [dbo].[UserCollectionDetails](@UserId) - GROUP BY - Id, - OrganizationId, - [Name], - CreationDate, - RevisionDate, - ExternalId -END -GO - -CREATE OR ALTER PROCEDURE [dbo].[Collection_ReadWithGroupsAndUsersByUserId] - @UserId UNIQUEIDENTIFIER -AS -BEGIN - SET NOCOUNT ON - - DECLARE @TempUserCollections TABLE( - Id UNIQUEIDENTIFIER, - OrganizationId UNIQUEIDENTIFIER, - Name VARCHAR(MAX), - CreationDate DATETIME2(7), - RevisionDate DATETIME2(7), - ExternalId NVARCHAR(300), - ReadOnly BIT, - HidePasswords BIT, - Manage BIT) - - INSERT INTO @TempUserCollections EXEC [dbo].[Collection_ReadByUserId] @UserId - - SELECT - * - FROM - @TempUserCollections C - - SELECT - CG.* - FROM - [dbo].[CollectionGroup] CG - INNER JOIN - @TempUserCollections C ON C.[Id] = CG.[CollectionId] - - SELECT - CU.* - FROM - [dbo].[CollectionUser] CU - INNER JOIN - @TempUserCollections C ON C.[Id] = CU.[CollectionId] - -END -GO - -CREATE OR ALTER PROCEDURE [dbo].[Group_ReadWithCollectionsById] - @Id UNIQUEIDENTIFIER -AS -BEGIN - SET NOCOUNT ON - - EXEC [dbo].[Group_ReadById] @Id - - SELECT - [CollectionId] [Id], - [ReadOnly], - [HidePasswords], - [Manage] - FROM - [dbo].[CollectionGroup] - WHERE - [GroupId] = @Id -END -GO diff --git a/util/Migrator/DbScripts/2023-08-25_00_BulkAddCollectionAccess.sql b/util/Migrator/DbScripts/2023-10-24_03_BulkAddCollectionAccess.sql similarity index 89% rename from util/Migrator/DbScripts/2023-08-25_00_BulkAddCollectionAccess.sql rename to util/Migrator/DbScripts/2023-10-24_03_BulkAddCollectionAccess.sql index d45d7dc5bd..f185624b0a 100644 --- a/util/Migrator/DbScripts/2023-08-25_00_BulkAddCollectionAccess.sql +++ b/util/Migrator/DbScripts/2023-10-24_03_BulkAddCollectionAccess.sql @@ -38,8 +38,8 @@ GO CREATE OR ALTER PROCEDURE [dbo].[Collection_CreateOrUpdateAccessForMany] @OrganizationId UNIQUEIDENTIFIER, @CollectionIds AS [dbo].[GuidIdArray] READONLY, - @Groups AS [dbo].[SelectionReadOnlyArray] READONLY, - @Users AS [dbo].[SelectionReadOnlyArray] READONLY + @Groups AS [dbo].[CollectionAccessSelectionType] READONLY, + @Users AS [dbo].[CollectionAccessSelectionType] READONLY AS BEGIN SET NOCOUNT ON @@ -78,7 +78,15 @@ BEGIN [Target].[HidePasswords] = [Source].[HidePasswords], [Target].[Manage] = [Source].[Manage] WHEN NOT MATCHED BY TARGET - THEN INSERT VALUES + THEN INSERT + ( + [CollectionId], + [GroupId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + VALUES ( [Source].[CollectionId], [Source].[GroupId], @@ -121,7 +129,15 @@ BEGIN [Target].[HidePasswords] = [Source].[HidePasswords], [Target].[Manage] = [Source].[Manage] WHEN NOT MATCHED BY TARGET - THEN INSERT VALUES + THEN INSERT + ( + [CollectionId], + [OrganizationUserId], + [ReadOnly], + [HidePasswords], + [Manage] + ) + VALUES ( [Source].[CollectionId], [Source].[OrganizationUserId], diff --git a/util/Migrator/DbScripts_finalization/2023-10-FutureMigrations.sql b/util/Migrator/DbScripts_finalization/2023-10-FutureMigrations.sql new file mode 100644 index 0000000000..e7dfd2f770 --- /dev/null +++ b/util/Migrator/DbScripts_finalization/2023-10-FutureMigrations.sql @@ -0,0 +1,50 @@ +-- Remove old stored procedures and SelectionReadOnlyArray for Flexible Collections +-- They have been superseded via their respective _V2 variants and the CollectionAccessSelectionType + +IF OBJECT_ID('[dbo].[CollectionUser_UpdateUsers]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[CollectionUser_UpdateUsers] +END +GO + +IF OBJECT_ID('[dbo].[Group_UpdateWithCollections]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Group_UpdateWithCollections] +END +GO + +IF OBJECT_ID('[dbo].[Collection_UpdateWithGroupsAndUsers]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Collection_UpdateWithGroupsAndUsers] +END +GO + +IF OBJECT_ID('[dbo].[OrganizationUser_UpdateWithCollections]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[OrganizationUser_UpdateWithCollections] +END +GO + +IF OBJECT_ID('[dbo].[Group_CreateWithCollections]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Group_CreateWithCollections] +END +GO + +IF OBJECT_ID('[dbo].[OrganizationUser_CreateWithCollections]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[OrganizationUser_CreateWithCollections] +END +GO + +IF OBJECT_ID('[dbo].[Collection_CreateWithGroupsAndUsers]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Collection_CreateWithGroupsAndUsers] +END +GO + +IF TYPE_ID('[dbo].[SelectionReadOnlyArray]') IS NOT NULL +BEGIN + DROP TYPE [dbo].[SelectionReadOnlyArray] +END +GO diff --git a/util/MsSqlMigratorUtility/Dockerfile b/util/MsSqlMigratorUtility/Dockerfile index 314fa664ef..3678d429e3 100644 --- a/util/MsSqlMigratorUtility/Dockerfile +++ b/util/MsSqlMigratorUtility/Dockerfile @@ -5,4 +5,6 @@ LABEL com.bitwarden.product="bitwarden" WORKDIR /app COPY obj/build-output/publish . -CMD ["sh", "-c", "dotnet /app/MsSqlMigratorUtility.dll \"${MSSQL_CONN_STRING}\" -v"] +ENTRYPOINT ["sh", "-c", "dotnet /app/MsSqlMigratorUtility.dll \"${MSSQL_CONN_STRING}\""] + +CMD [ "-v" ] diff --git a/util/MySqlMigrations/Migrations/20230807181649_LimitCollectionCreateDelete.Designer.cs b/util/MySqlMigrations/Migrations/20231024181649_LimitCollectionCreateDelete.Designer.cs similarity index 100% rename from util/MySqlMigrations/Migrations/20230807181649_LimitCollectionCreateDelete.Designer.cs rename to util/MySqlMigrations/Migrations/20231024181649_LimitCollectionCreateDelete.Designer.cs diff --git a/util/MySqlMigrations/Migrations/20230807181649_LimitCollectionCreateDelete.cs b/util/MySqlMigrations/Migrations/20231024181649_LimitCollectionCreateDelete.cs similarity index 100% rename from util/MySqlMigrations/Migrations/20230807181649_LimitCollectionCreateDelete.cs rename to util/MySqlMigrations/Migrations/20231024181649_LimitCollectionCreateDelete.cs diff --git a/util/MySqlMigrations/Migrations/20230720203306_2023-07-11_00_CollectionManagePermission.sql.Designer.cs b/util/MySqlMigrations/Migrations/20231024203306_CollectionManagePermission.sql.Designer.cs similarity index 100% rename from util/MySqlMigrations/Migrations/20230720203306_2023-07-11_00_CollectionManagePermission.sql.Designer.cs rename to util/MySqlMigrations/Migrations/20231024203306_CollectionManagePermission.sql.Designer.cs diff --git a/util/MySqlMigrations/Migrations/20230720203306_2023-07-11_00_CollectionManagePermission.sql.cs b/util/MySqlMigrations/Migrations/20231024203306_CollectionManagePermission.sql.cs similarity index 100% rename from util/MySqlMigrations/Migrations/20230720203306_2023-07-11_00_CollectionManagePermission.sql.cs rename to util/MySqlMigrations/Migrations/20231024203306_CollectionManagePermission.sql.cs diff --git a/util/SqliteMigrations/Migrations/20230807181657_LimitCollectionCreateDelete.Designer.cs b/util/SqliteMigrations/Migrations/20231024181657_LimitCollectionCreateDelete.Designer.cs similarity index 100% rename from util/SqliteMigrations/Migrations/20230807181657_LimitCollectionCreateDelete.Designer.cs rename to util/SqliteMigrations/Migrations/20231024181657_LimitCollectionCreateDelete.Designer.cs diff --git a/util/SqliteMigrations/Migrations/20230807181657_LimitCollectionCreateDelete.cs b/util/SqliteMigrations/Migrations/20231024181657_LimitCollectionCreateDelete.cs similarity index 100% rename from util/SqliteMigrations/Migrations/20230807181657_LimitCollectionCreateDelete.cs rename to util/SqliteMigrations/Migrations/20231024181657_LimitCollectionCreateDelete.cs diff --git a/util/SqliteMigrations/Migrations/20230720200803_2023-07-11_00_CollectionManagePermission.sql.Designer.cs b/util/SqliteMigrations/Migrations/20231024200803_CollectionManagePermission.sql.Designer.cs similarity index 100% rename from util/SqliteMigrations/Migrations/20230720200803_2023-07-11_00_CollectionManagePermission.sql.Designer.cs rename to util/SqliteMigrations/Migrations/20231024200803_CollectionManagePermission.sql.Designer.cs diff --git a/util/SqliteMigrations/Migrations/20230720200803_2023-07-11_00_CollectionManagePermission.sql.cs b/util/SqliteMigrations/Migrations/20231024200803_CollectionManagePermission.sql.cs similarity index 100% rename from util/SqliteMigrations/Migrations/20230720200803_2023-07-11_00_CollectionManagePermission.sql.cs rename to util/SqliteMigrations/Migrations/20231024200803_CollectionManagePermission.sql.cs