1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-03 00:52:49 -05:00

Merge branch 'feature/flexible-collections' into flexible-collections/deprecate-custom-collection-perm

# Conflicts:
#	src/Api/AdminConsole/Controllers/GroupsController.cs
#	src/Api/AdminConsole/Controllers/OrganizationUsersController.cs
#	src/Core/Services/Implementations/OrganizationService.cs
This commit is contained in:
Rui Tome
2023-10-25 16:29:21 +01:00
255 changed files with 3223 additions and 1605 deletions

10
.github/CODEOWNERS vendored
View File

@ -9,6 +9,11 @@
# DevOps for Actions and other workflow changes. # DevOps for Actions and other workflow changes.
.github/workflows @bitwarden/dept-devops .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 team files ##
**/Auth @bitwarden/team-auth-dev **/Auth @bitwarden/team-auth-dev
bitwarden_license/src/Sso @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 **/SecretsManager @bitwarden/team-secrets-manager-dev
**/Tools @bitwarden/team-tools-dev **/Tools @bitwarden/team-tools-dev
## Vault Team files
**/Vault @bitwarden/team-vault-dev **/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 # Admin-Console Team
**/AdminConsole @bitwarden/team-admin-console-dev
bitwarden_license/src/Scim @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.IntegrationTest @bitwarden/team-admin-console-dev
bitwarden_license/src/test/Scim.ScimTest @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 Team
**/*billing* @bitwarden/team-billing-dev **/*billing* @bitwarden/team-billing-dev

View File

@ -277,7 +277,7 @@ jobs:
- name: Retrieve github PAT secrets - name: Retrieve github PAT secrets
id: retrieve-secret-pat id: retrieve-secret-pat
uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf
with: with:
keyvault: "bitwarden-ci" keyvault: "bitwarden-ci"
secrets: "github-pat-bitwarden-devops-bot-repo-scope" secrets: "github-pat-bitwarden-devops-bot-repo-scope"
@ -528,7 +528,7 @@ jobs:
- name: Retrieve github PAT secrets - name: Retrieve github PAT secrets
id: retrieve-secret-pat id: retrieve-secret-pat
uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf
with: with:
keyvault: "bitwarden-ci" keyvault: "bitwarden-ci"
secrets: "github-pat-bitwarden-devops-bot-repo-scope" secrets: "github-pat-bitwarden-devops-bot-repo-scope"
@ -603,7 +603,7 @@ jobs:
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf
if: failure() if: failure()
with: with:
keyvault: "bitwarden-ci" keyvault: "bitwarden-ci"

View File

@ -92,7 +92,7 @@ jobs:
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf
if: failure() if: failure()
with: with:
keyvault: "bitwarden-ci" keyvault: "bitwarden-ci"

View File

@ -41,7 +41,7 @@ jobs:
- name: Check Release Version - name: Check Release Version
id: version id: version
uses: bitwarden/gh-actions/release-version-check@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 uses: bitwarden/gh-actions/release-version-check@c970b0fb89bd966749280e832928db62040812bf
with: with:
release-type: ${{ github.event.inputs.release_type }} release-type: ${{ github.event.inputs.release_type }}
project-type: dotnet project-type: dotnet
@ -89,7 +89,7 @@ jobs:
- name: Download latest Release ${{ matrix.name }} asset - name: Download latest Release ${{ matrix.name }} asset
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
@ -98,7 +98,7 @@ jobs:
- name: Dry Run - Download latest Release ${{ matrix.name }} asset - name: Dry Run - Download latest Release ${{ matrix.name }} asset
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
@ -274,7 +274,7 @@ jobs:
steps: steps:
- name: Download latest Release Docker Stubs - name: Download latest Release Docker Stubs
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
@ -287,7 +287,7 @@ jobs:
- name: Dry Run - Download latest Release Docker Stubs - name: Dry Run - Download latest Release Docker Stubs
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 uses: bitwarden/gh-actions/download-artifacts@c970b0fb89bd966749280e832928db62040812bf
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success

View File

@ -23,7 +23,7 @@ jobs:
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 uses: bitwarden/gh-actions/get-keyvault-secrets@c970b0fb89bd966749280e832928db62040812bf
with: with:
keyvault: "bitwarden-ci" keyvault: "bitwarden-ci"
secrets: "github-gpg-private-key, github-gpg-private-key-passphrase" 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 }} run: git switch -c version_bump_${{ github.event.inputs.version_number }}
- name: Bump Version - Props - name: Bump Version - Props
uses: bitwarden/gh-actions/version-bump@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 uses: bitwarden/gh-actions/version-bump@c970b0fb89bd966749280e832928db62040812bf
with: with:
version: ${{ github.event.inputs.version_number }} version: ${{ github.event.inputs.version_number }}
file_path: "Directory.Build.props" file_path: "Directory.Build.props"

View File

@ -8,4 +8,4 @@ on:
jobs: jobs:
call-workflow: call-workflow:
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@f1125802b1ccae8c601d7c4f61ce39ea254b10c8 uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@c970b0fb89bd966749280e832928db62040812bf

2
.gitignore vendored
View File

@ -225,4 +225,4 @@ src/Identity/Identity.zip
src/Notifications/Notifications.zip src/Notifications/Notifications.zip
bitwarden_license/src/Portal/Portal.zip bitwarden_license/src/Portal/Portal.zip
bitwarden_license/src/Sso/Sso.zip bitwarden_license/src/Sso/Sso.zip
src/Api/flags.json **/src/*/flags.json

View File

@ -1,4 +1,5 @@
using Bit.Core.Context; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.SecretsManager.AuthorizationRequirements; using Bit.Core.SecretsManager.AuthorizationRequirements;

View File

@ -56,6 +56,9 @@ public class
case not null when requirement == ServiceAccountOperations.RevokeAccessTokens: case not null when requirement == ServiceAccountOperations.RevokeAccessTokens:
await CanRevokeAccessTokensAsync(context, requirement, resource); await CanRevokeAccessTokensAsync(context, requirement, resource);
break; break;
case not null when requirement == ServiceAccountOperations.ReadEvents:
await CanReadEventsAsync(context, requirement, resource);
break;
default: default:
throw new ArgumentException("Unsupported operation requirement type provided.", throw new ArgumentException("Unsupported operation requirement type provided.",
nameof(requirement)); nameof(requirement));
@ -169,4 +172,19 @@ public class
context.Succeed(requirement); 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);
}
}
} }

View File

@ -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.Exceptions;
using Bit.Core.OrganizationFeatures.Groups.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Scim.Groups.Interfaces; using Bit.Scim.Groups.Interfaces;
using Bit.Scim.Models; using Bit.Scim.Models;

View File

@ -1,5 +1,5 @@
using Bit.Core.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Scim.Groups.Interfaces; using Bit.Scim.Groups.Interfaces;
namespace Bit.Scim.Groups; namespace Bit.Scim.Groups;

View File

@ -1,4 +1,4 @@
using Bit.Core.Entities; using Bit.Core.AdminConsole.Entities;
namespace Bit.Scim.Groups.Interfaces; namespace Bit.Scim.Groups.Interfaces;

View File

@ -1,4 +1,5 @@
using Bit.Core.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.Entities;
using Bit.Scim.Models; using Bit.Scim.Models;
namespace Bit.Scim.Groups.Interfaces; namespace Bit.Scim.Groups.Interfaces;

View File

@ -1,4 +1,5 @@
using Bit.Core.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.Entities;
using Bit.Scim.Models; using Bit.Scim.Models;
namespace Bit.Scim.Groups.Interfaces; namespace Bit.Scim.Groups.Interfaces;

View File

@ -1,10 +1,10 @@
using System.Text.Json; 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.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; 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.Groups.Interfaces;
using Bit.Scim.Models; using Bit.Scim.Models;

View File

@ -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.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.OrganizationFeatures.Groups.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Scim.Context; using Bit.Scim.Context;
using Bit.Scim.Groups.Interfaces; using Bit.Scim.Groups.Interfaces;

View File

@ -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.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.OrganizationFeatures.Groups.Interfaces;
using Bit.Core.Repositories;
using Bit.Scim.Context; using Bit.Scim.Context;
using Bit.Scim.Groups.Interfaces; using Bit.Scim.Groups.Interfaces;
using Bit.Scim.Models; using Bit.Scim.Models;

View File

@ -1,4 +1,4 @@
using Bit.Core.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Scim.Models; namespace Bit.Scim.Models;

View File

@ -1,4 +1,4 @@
using Bit.Core.Entities; using Bit.Core.AdminConsole.Entities;
namespace Bit.Scim.Models; namespace Bit.Scim.Models;

View File

@ -2,6 +2,8 @@
using System.Security.Claims; using System.Security.Claims;
using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.AccessPolicies; using Bit.Commercial.Core.SecretsManager.AuthorizationHandlers.AccessPolicies;
using Bit.Commercial.Core.Test.SecretsManager.Enums; using Bit.Commercial.Core.Test.SecretsManager.Enums;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;

View File

@ -497,4 +497,63 @@ public class ServiceAccountAuthorizationHandlerTests
Assert.Equal(expected, authzContext.HasSucceeded); Assert.Equal(expected, authzContext.HasSucceeded);
} }
[Theory]
[BitAutoData]
public async Task CanReadEvents_AccessToSecretsManagerFalse_DoesNotSucceed(
SutProvider<ServiceAccountAuthorizationHandler> sutProvider, ServiceAccount serviceAccount,
ClaimsPrincipal claimsPrincipal)
{
var requirement = ServiceAccountOperations.ReadEvents;
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(serviceAccount.OrganizationId)
.Returns(false);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, serviceAccount);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.False(authzContext.HasSucceeded);
}
[Theory]
[BitAutoData]
public async Task CanReadEvents_NullResource_DoesNotSucceed(
SutProvider<ServiceAccountAuthorizationHandler> sutProvider, ServiceAccount serviceAccount,
ClaimsPrincipal claimsPrincipal,
Guid userId)
{
var requirement = ServiceAccountOperations.ReadEvents;
SetupPermission(sutProvider, PermissionType.RunAsAdmin, serviceAccount.OrganizationId, userId);
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { 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<ServiceAccountAuthorizationHandler> sutProvider, ServiceAccount serviceAccount,
ClaimsPrincipal claimsPrincipal,
Guid userId)
{
var requirement = ServiceAccountOperations.ReadEvents;
SetupPermission(sutProvider, permissionType, serviceAccount.OrganizationId, userId);
sutProvider.GetDependency<IServiceAccountRepository>()
.AccessToServiceAccountAsync(serviceAccount.Id, userId, Arg.Any<AccessClientType>())
.Returns((read, write));
var authzContext = new AuthorizationHandlerContext(new List<IAuthorizationRequirement> { requirement },
claimsPrincipal, serviceAccount);
await sutProvider.Sut.HandleAsync(authzContext);
Assert.Equal(expected, authzContext.HasSucceeded);
}
} }

View File

@ -1,5 +1,5 @@
using Bit.Core.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Scim.Groups; using Bit.Scim.Groups;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;

View File

@ -1,10 +1,11 @@
using System.Text.Json; 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.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; 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.Groups;
using Bit.Scim.Models; using Bit.Scim.Models;
using Bit.Scim.Utilities; using Bit.Scim.Utilities;

View File

@ -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.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.OrganizationFeatures.Groups.Interfaces;
using Bit.Core.Repositories;
using Bit.Scim.Context; using Bit.Scim.Context;
using Bit.Scim.Groups; using Bit.Scim.Groups;
using Bit.Scim.Models; using Bit.Scim.Models;

View File

@ -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.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.OrganizationFeatures.Groups.Interfaces;
using Bit.Core.Repositories;
using Bit.Scim.Context; using Bit.Scim.Context;
using Bit.Scim.Groups; using Bit.Scim.Groups;
using Bit.Scim.Models; using Bit.Scim.Models;

View File

@ -2,6 +2,8 @@
using Bit.Admin.Models; using Bit.Admin.Models;
using Bit.Admin.Services; using Bit.Admin.Services;
using Bit.Admin.Utilities; using Bit.Admin.Utilities;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;

View File

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Entities.Provider; using Bit.Core.Entities.Provider;
using Bit.Core.Enums; using Bit.Core.Enums;

View File

@ -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.Entities.Provider;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.OrganizationUsers;

View File

@ -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.Models.Response;
using Bit.Api.Vault.AuthorizationHandlers.Groups; using Bit.Api.Vault.AuthorizationHandlers.Groups;
using Bit.Core; 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.Context;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.OrganizationFeatures.Groups.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers; namespace Bit.Api.AdminConsole.Controllers;
[Route("organizations/{orgId}/groups")] [Route("organizations/{orgId}/groups")]
[Authorize("Application")] [Authorize("Application")]

View File

@ -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;
using Bit.Api.Models.Response.Organizations;
using Bit.Api.Vault.AuthorizationHandlers.OrganizationUsers; using Bit.Api.Vault.AuthorizationHandlers.OrganizationUsers;
using Bit.Core; using Bit.Core;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
@ -16,7 +18,7 @@ using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers; namespace Bit.Api.AdminConsole.Controllers;
[Route("organizations/{orgId}/users")] [Route("organizations/{orgId}/users")]
[Authorize("Application")] [Authorize("Application")]

View File

@ -1,4 +1,8 @@
using System.Text.Json; 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.Accounts;
using Bit.Api.Auth.Models.Request.Organizations; using Bit.Api.Auth.Models.Request.Organizations;
using Bit.Api.Auth.Models.Response.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.Accounts;
using Bit.Api.Models.Request.Organizations; using Bit.Api.Models.Request.Organizations;
using Bit.Api.Models.Response; using Bit.Api.Models.Response;
using Bit.Api.Models.Response.Organizations;
using Bit.Core; using Bit.Core;
using Bit.Core.Auth.Enums; using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Repositories; using Bit.Core.Auth.Repositories;
@ -26,7 +29,7 @@ using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers; namespace Bit.Api.AdminConsole.Controllers;
[Route("organizations")] [Route("organizations")]
[Authorize("Application")] [Authorize("Application")]

View File

@ -1,4 +1,4 @@
using Bit.Api.Models.Request; using Bit.Api.AdminConsole.Models.Request;
using Bit.Api.Models.Response; using Bit.Api.Models.Response;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;
@ -12,7 +12,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers; namespace Bit.Api.AdminConsole.Controllers;
[Route("organizations/{orgId}/policies")] [Route("organizations/{orgId}/policies")]
[Authorize("Application")] [Authorize("Application")]

View File

@ -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;
using Bit.Api.Models.Response.Providers;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Repositories; using Bit.Core.Repositories;
@ -9,7 +9,7 @@ using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers; namespace Bit.Api.AdminConsole.Controllers;
[Route("providers/{providerId:guid}/organizations")] [Route("providers/{providerId:guid}/organizations")]
[Authorize("Application")] [Authorize("Application")]

View File

@ -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;
using Bit.Api.Models.Response.Providers;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Business.Provider; using Bit.Core.Models.Business.Provider;
@ -9,7 +9,7 @@ using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers; namespace Bit.Api.AdminConsole.Controllers;
[Route("providers/{providerId:guid}/users")] [Route("providers/{providerId:guid}/users")]
[Authorize("Application")] [Authorize("Application")]

View File

@ -1,5 +1,5 @@
using Bit.Api.Models.Request.Providers; using Bit.Api.AdminConsole.Models.Request.Providers;
using Bit.Api.Models.Response.Providers; using Bit.Api.AdminConsole.Models.Response.Providers;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Repositories; using Bit.Core.Repositories;
@ -8,7 +8,7 @@ using Bit.Core.Settings;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers; namespace Bit.Api.AdminConsole.Controllers;
[Route("providers")] [Route("providers")]
[Authorize("Application")] [Authorize("Application")]

View File

@ -1,7 +1,8 @@
using System.ComponentModel.DataAnnotations; 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 public class GroupRequestModel
{ {

View File

@ -1,7 +1,8 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
namespace Bit.Api.Models.Request.Organizations; namespace Bit.Api.AdminConsole.Models.Request;
public class ImportOrganizationUsersRequestModel public class ImportOrganizationUsersRequestModel
{ {
@ -24,7 +25,7 @@ public class ImportOrganizationUsersRequestModel
{ {
var importedGroup = new ImportedGroup var importedGroup = new ImportedGroup
{ {
Group = new Core.Entities.Group Group = new Core.AdminConsole.Entities.Group
{ {
OrganizationId = organizationId, OrganizationId = organizationId,
Name = Name, Name = Name,

View File

@ -1,7 +1,7 @@
using Bit.Api.Auth.Models.Request.Accounts; using Bit.Api.Auth.Models.Request.Accounts;
using Bit.Core.Enums; using Bit.Core.Enums;
namespace Bit.Api.Models.Request.Accounts; namespace Bit.Api.AdminConsole.Models.Request.Organizations;
public class OrganizationApiKeyRequestModel : SecretVerificationRequestModel public class OrganizationApiKeyRequestModel : SecretVerificationRequestModel
{ {

View File

@ -4,7 +4,7 @@ using Bit.Core.Enums;
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Api.Models.Request.Organizations; namespace Bit.Api.AdminConsole.Models.Request.Organizations;
public class OrganizationCreateRequestModel : IValidatableObject public class OrganizationCreateRequestModel : IValidatableObject
{ {

View File

@ -2,7 +2,7 @@
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
namespace Bit.Api.Models.Request.Organizations; namespace Bit.Api.AdminConsole.Models.Request.Organizations;
public class OrganizationKeysRequestModel public class OrganizationKeysRequestModel
{ {

View File

@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Models.Request.Organizations; namespace Bit.Api.AdminConsole.Models.Request.Organizations;
public class OrganizationSeatRequestModel : IValidatableObject public class OrganizationSeatRequestModel : IValidatableObject
{ {

View File

@ -3,7 +3,7 @@ using Bit.Core.Entities;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Settings; using Bit.Core.Settings;
namespace Bit.Api.Models.Request.Organizations; namespace Bit.Api.AdminConsole.Models.Request.Organizations;
public class OrganizationUpdateRequestModel public class OrganizationUpdateRequestModel
{ {

View File

@ -2,7 +2,7 @@
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
namespace Bit.Api.Models.Request.Organizations; namespace Bit.Api.AdminConsole.Models.Request.Organizations;
public class OrganizationUpgradeRequestModel public class OrganizationUpgradeRequestModel
{ {

View File

@ -1,11 +1,12 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Bit.Api.Models.Request;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Api.Models.Request.Organizations; namespace Bit.Api.AdminConsole.Models.Request.Organizations;
public class OrganizationUserInviteRequestModel public class OrganizationUserInviteRequestModel
{ {

View File

@ -3,7 +3,7 @@ using System.Text.Json;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
namespace Bit.Api.Models.Request; namespace Bit.Api.AdminConsole.Models.Request;
public class PolicyRequestModel public class PolicyRequestModel
{ {

View File

@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Models.Request.Providers; namespace Bit.Api.AdminConsole.Models.Request.Providers;
public class ProviderOrganizationAddRequestModel public class ProviderOrganizationAddRequestModel
{ {

View File

@ -1,8 +1,8 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Bit.Api.Models.Request.Organizations; using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Api.Models.Request.Providers; namespace Bit.Api.AdminConsole.Models.Request.Providers;
public class ProviderOrganizationCreateRequestModel public class ProviderOrganizationCreateRequestModel
{ {

View File

@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities.Provider; using Bit.Core.Entities.Provider;
namespace Bit.Api.Models.Request.Providers; namespace Bit.Api.AdminConsole.Models.Request.Providers;
public class ProviderSetupRequestModel public class ProviderSetupRequestModel
{ {

View File

@ -2,7 +2,7 @@
using Bit.Core.Entities.Provider; using Bit.Core.Entities.Provider;
using Bit.Core.Settings; using Bit.Core.Settings;
namespace Bit.Api.Models.Request.Providers; namespace Bit.Api.AdminConsole.Models.Request.Providers;
public class ProviderUpdateRequestModel public class ProviderUpdateRequestModel
{ {

View File

@ -3,7 +3,7 @@ using Bit.Core.Entities.Provider;
using Bit.Core.Enums.Provider; using Bit.Core.Enums.Provider;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Api.Models.Request.Providers; namespace Bit.Api.AdminConsole.Models.Request.Providers;
public class ProviderUserInviteRequestModel public class ProviderUserInviteRequestModel
{ {

View File

@ -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.Api;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
namespace Bit.Api.Models.Response; namespace Bit.Api.AdminConsole.Models.Response;
public class GroupResponseModel : ResponseModel public class GroupResponseModel : ResponseModel
{ {

View File

@ -2,7 +2,7 @@
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Api; using Bit.Core.Models.Api;
namespace Bit.Api.Models.Response.Organizations; namespace Bit.Api.AdminConsole.Models.Response.Organizations;
public class OrganizationApiKeyInformation : ResponseModel public class OrganizationApiKeyInformation : ResponseModel
{ {

View File

@ -1,6 +1,6 @@
using Bit.Core.Models.Api; using Bit.Core.Models.Api;
namespace Bit.Api.Models.Response.Organizations; namespace Bit.Api.AdminConsole.Models.Response.Organizations;
public class OrganizationAutoEnrollStatusResponseModel : ResponseModel public class OrganizationAutoEnrollStatusResponseModel : ResponseModel
{ {

View File

@ -1,7 +1,7 @@
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Models.Api; using Bit.Core.Models.Api;
namespace Bit.Api.Models.Response.Organizations; namespace Bit.Api.AdminConsole.Models.Response.Organizations;
public class OrganizationKeysResponseModel : ResponseModel public class OrganizationKeysResponseModel : ResponseModel
{ {

View File

@ -1,7 +1,7 @@
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Models.Api; using Bit.Core.Models.Api;
namespace Bit.Api.Models.Response.Organizations; namespace Bit.Api.AdminConsole.Models.Response.Organizations;
public class OrganizationPublicKeyResponseModel : ResponseModel public class OrganizationPublicKeyResponseModel : ResponseModel
{ {

View File

@ -1,11 +1,12 @@
using Bit.Core.Entities; using Bit.Api.Models.Response;
using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Api; using Bit.Core.Models.Api;
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Constants = Bit.Core.Constants; using Constants = Bit.Core.Constants;
namespace Bit.Api.Models.Response.Organizations; namespace Bit.Api.AdminConsole.Models.Response.Organizations;
public class OrganizationResponseModel : ResponseModel public class OrganizationResponseModel : ResponseModel
{ {
@ -113,7 +114,7 @@ public class OrganizationSubscriptionResponseModel : OrganizationResponseModel
{ {
Subscription = subscription.Subscription != null ? new BillingSubscription(subscription.Subscription) : null; Subscription = subscription.Subscription != null ? new BillingSubscription(subscription.Subscription) : null;
UpcomingInvoice = subscription.UpcomingInvoice != null ? new BillingSubscriptionUpcomingInvoice(subscription.UpcomingInvoice) : 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. Expiration = DateTime.UtcNow.AddYears(1); // Not used, so just give it a value.
if (hideSensitiveData) if (hideSensitiveData)
@ -144,7 +145,7 @@ public class OrganizationSubscriptionResponseModel : OrganizationResponseModel
public string StorageName { get; set; } public string StorageName { get; set; }
public double? StorageGb { get; set; } public double? StorageGb { get; set; }
public BillingCustomerDiscount Discount { get; set; } public BillingCustomerDiscount CustomerDiscount { get; set; }
public BillingSubscription Subscription { get; set; } public BillingSubscription Subscription { get; set; }
public BillingSubscriptionUpcomingInvoice UpcomingInvoice { get; set; } public BillingSubscriptionUpcomingInvoice UpcomingInvoice { get; set; }

View File

@ -1,4 +1,5 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Bit.Api.Models.Response;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Api; using Bit.Core.Models.Api;
@ -6,7 +7,7 @@ using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Api.Models.Response.Organizations; namespace Bit.Api.AdminConsole.Models.Response.Organizations;
public class OrganizationUserResponseModel : ResponseModel public class OrganizationUserResponseModel : ResponseModel
{ {

View File

@ -7,7 +7,7 @@ using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Api.Models.Response; namespace Bit.Api.AdminConsole.Models.Response;
public class ProfileOrganizationResponseModel : ResponseModel public class ProfileOrganizationResponseModel : ResponseModel
{ {

View File

@ -2,7 +2,7 @@
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Api.Models.Response; namespace Bit.Api.AdminConsole.Models.Response;
public class ProfileProviderOrganizationResponseModel : ProfileOrganizationResponseModel public class ProfileProviderOrganizationResponseModel : ProfileOrganizationResponseModel
{ {

View File

@ -3,7 +3,7 @@ using Bit.Core.Models.Api;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Api.Models.Response.Providers; namespace Bit.Api.AdminConsole.Models.Response.Providers;
public class ProfileProviderResponseModel : ResponseModel public class ProfileProviderResponseModel : ResponseModel
{ {

View File

@ -2,7 +2,7 @@
using Bit.Core.Models.Api; using Bit.Core.Models.Api;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
namespace Bit.Api.Models.Response.Providers; namespace Bit.Api.AdminConsole.Models.Response.Providers;
public class ProviderOrganizationResponseModel : ResponseModel public class ProviderOrganizationResponseModel : ResponseModel
{ {

View File

@ -1,7 +1,7 @@
using Bit.Core.Entities.Provider; using Bit.Core.Entities.Provider;
using Bit.Core.Models.Api; using Bit.Core.Models.Api;
namespace Bit.Api.Models.Response.Providers; namespace Bit.Api.AdminConsole.Models.Response.Providers;
public class ProviderResponseModel : ResponseModel public class ProviderResponseModel : ResponseModel
{ {

View File

@ -4,7 +4,7 @@ using Bit.Core.Models.Api;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Api.Models.Response.Providers; namespace Bit.Api.AdminConsole.Models.Response.Providers;
public class ProviderUserResponseModel : ResponseModel public class ProviderUserResponseModel : ResponseModel
{ {

View File

@ -1,13 +1,15 @@
using System.Net; 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.Api.Models.Public.Response;
using Bit.Core.AdminConsole.OrganizationFeatures.Groups.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.OrganizationFeatures.Groups.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Public.Controllers; namespace Bit.Api.AdminConsole.Public.Controllers;
[Route("public/groups")] [Route("public/groups")]
[Authorize("Organization")] [Authorize("Organization")]

View File

@ -1,6 +1,8 @@
using System.Net; 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.Api.Models.Public.Response;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
@ -9,7 +11,7 @@ using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Public.Controllers; namespace Bit.Api.AdminConsole.Public.Controllers;
[Route("public/members")] [Route("public/members")]
[Authorize("Organization")] [Authorize("Organization")]

View File

@ -1,5 +1,6 @@
using System.Net; 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.Api.Models.Public.Response;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
@ -8,7 +9,7 @@ using Bit.Core.Settings;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Public.Controllers; namespace Bit.Api.AdminConsole.Public.Controllers;
[Route("public/organization")] [Route("public/organization")]
[Authorize("Organization")] [Authorize("Organization")]

View File

@ -1,5 +1,6 @@
using System.Net; 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.Api.Models.Public.Response;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;
@ -8,7 +9,7 @@ using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Public.Controllers; namespace Bit.Api.AdminConsole.Public.Controllers;
[Route("public/policies")] [Route("public/policies")]
[Authorize("Organization")] [Authorize("Organization")]

View File

@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Models.Public; namespace Bit.Api.AdminConsole.Public.Models;
public abstract class GroupBaseModel public abstract class GroupBaseModel
{ {

View File

@ -3,7 +3,7 @@ using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.OrganizationUsers;
namespace Bit.Api.Models.Public; namespace Bit.Api.AdminConsole.Public.Models;
public abstract class MemberBaseModel public abstract class MemberBaseModel
{ {

View File

@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace Bit.Api.Models.Public; namespace Bit.Api.AdminConsole.Public.Models;
public abstract class PolicyBaseModel public abstract class PolicyBaseModel
{ {

View File

@ -1,7 +1,7 @@
using Bit.Api.Auth.Models.Public.Request; 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 public class GroupCreateUpdateRequestModel : GroupBaseModel
{ {

View File

@ -2,7 +2,7 @@
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Api.Models.Public.Request; namespace Bit.Api.AdminConsole.Public.Models.Request;
public class MemberCreateRequestModel : MemberUpdateRequestModel public class MemberCreateRequestModel : MemberUpdateRequestModel
{ {

View File

@ -1,7 +1,7 @@
using Bit.Api.Auth.Models.Public.Request; using Bit.Api.Auth.Models.Public.Request;
using Bit.Core.Entities; using Bit.Core.Entities;
namespace Bit.Api.Models.Public.Request; namespace Bit.Api.AdminConsole.Public.Models.Request;
public class MemberUpdateRequestModel : MemberBaseModel public class MemberUpdateRequestModel : MemberBaseModel
{ {

View File

@ -1,10 +1,11 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization; 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.Models.Business;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Api.Models.Public.Request; namespace Bit.Api.AdminConsole.Public.Models.Request;
public class OrganizationImportRequestModel public class OrganizationImportRequestModel
{ {

View File

@ -1,7 +1,7 @@
using System.Text.Json; using System.Text.Json;
using Bit.Core.Entities; using Bit.Core.Entities;
namespace Bit.Api.Models.Public.Request; namespace Bit.Api.AdminConsole.Public.Models.Request;
public class PolicyUpdateRequestModel : PolicyBaseModel public class PolicyUpdateRequestModel : PolicyBaseModel
{ {

View File

@ -1,4 +1,4 @@
namespace Bit.Api.Models.Public.Request; namespace Bit.Api.AdminConsole.Public.Models.Request;
public class UpdateGroupIdsRequestModel public class UpdateGroupIdsRequestModel
{ {

View File

@ -1,4 +1,4 @@
namespace Bit.Api.Models.Public.Request; namespace Bit.Api.AdminConsole.Public.Models.Request;
public class UpdateMemberIdsRequestModel public class UpdateMemberIdsRequestModel
{ {

View File

@ -1,9 +1,10 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Bit.Api.Auth.Models.Public.Response; 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; using Bit.Core.Models.Data;
namespace Bit.Api.Models.Public.Response; namespace Bit.Api.AdminConsole.Public.Models.Response;
/// <summary> /// <summary>
/// A user group. /// A user group.

View File

@ -1,11 +1,12 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Bit.Api.Auth.Models.Public.Response; using Bit.Api.Auth.Models.Public.Response;
using Bit.Api.Models.Public.Response;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.OrganizationUsers;
namespace Bit.Api.Models.Public.Response; namespace Bit.Api.AdminConsole.Public.Models.Response;
/// <summary> /// <summary>
/// An organization member. /// An organization member.

View File

@ -1,9 +1,10 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Text.Json; using System.Text.Json;
using Bit.Api.Models.Public.Response;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
namespace Bit.Api.Models.Public.Response; namespace Bit.Api.AdminConsole.Public.Models.Response;
/// <summary> /// <summary>
/// A policy. /// A policy.

View File

@ -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.Auth.Models.Response;
using Bit.Api.Models.Request.Organizations;
using Bit.Api.Models.Response; using Bit.Api.Models.Response;
using Bit.Api.Vault.Models.Response; using Bit.Api.Vault.Models.Response;
using Bit.Core.Auth.Services; using Bit.Core.Auth.Services;

View File

@ -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;
using Bit.Api.Models.Request.Accounts; using Bit.Api.Models.Request.Accounts;
using Bit.Api.Models.Response; using Bit.Api.Models.Response;

View File

@ -1,4 +1,5 @@
using Bit.Api.Models.Response; using Bit.Api.Models.Response;
using Bit.Api.Utilities;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
@ -41,7 +42,7 @@ public class EventsController : Controller
public async Task<ListResponseModel<EventResponseModel>> GetUser( public async Task<ListResponseModel<EventResponseModel>> GetUser(
[FromQuery] DateTime? start = null, [FromQuery] DateTime? end = null, [FromQuery] string continuationToken = null) [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 userId = _userService.GetProperUserId(User).Value;
var result = await _eventRepository.GetManyByUserAsync(userId, dateRange.Item1, dateRange.Item2, var result = await _eventRepository.GetManyByUserAsync(userId, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken }); new PageOptions { ContinuationToken = continuationToken });
@ -75,7 +76,7 @@ public class EventsController : Controller
throw new NotFoundException(); throw new NotFoundException();
} }
var dateRange = GetDateRange(start, end); var dateRange = ApiHelpers.GetDateRange(start, end);
var result = await _eventRepository.GetManyByCipherAsync(cipher, dateRange.Item1, dateRange.Item2, var result = await _eventRepository.GetManyByCipherAsync(cipher, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken }); new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e)); var responses = result.Data.Select(e => new EventResponseModel(e));
@ -92,7 +93,7 @@ public class EventsController : Controller
throw new NotFoundException(); throw new NotFoundException();
} }
var dateRange = GetDateRange(start, end); var dateRange = ApiHelpers.GetDateRange(start, end);
var result = await _eventRepository.GetManyByOrganizationAsync(orgId, dateRange.Item1, dateRange.Item2, var result = await _eventRepository.GetManyByOrganizationAsync(orgId, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken }); new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e)); var responses = result.Data.Select(e => new EventResponseModel(e));
@ -110,7 +111,7 @@ public class EventsController : Controller
throw new NotFoundException(); throw new NotFoundException();
} }
var dateRange = GetDateRange(start, end); var dateRange = ApiHelpers.GetDateRange(start, end);
var result = await _eventRepository.GetManyByOrganizationActingUserAsync(organizationUser.OrganizationId, var result = await _eventRepository.GetManyByOrganizationActingUserAsync(organizationUser.OrganizationId,
organizationUser.UserId.Value, dateRange.Item1, dateRange.Item2, organizationUser.UserId.Value, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken }); new PageOptions { ContinuationToken = continuationToken });
@ -127,7 +128,7 @@ public class EventsController : Controller
throw new NotFoundException(); throw new NotFoundException();
} }
var dateRange = GetDateRange(start, end); var dateRange = ApiHelpers.GetDateRange(start, end);
var result = await _eventRepository.GetManyByProviderAsync(providerId, dateRange.Item1, dateRange.Item2, var result = await _eventRepository.GetManyByProviderAsync(providerId, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken }); new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e)); var responses = result.Data.Select(e => new EventResponseModel(e));
@ -145,33 +146,11 @@ public class EventsController : Controller
throw new NotFoundException(); throw new NotFoundException();
} }
var dateRange = GetDateRange(start, end); var dateRange = ApiHelpers.GetDateRange(start, end);
var result = await _eventRepository.GetManyByProviderActingUserAsync(providerUser.ProviderId, var result = await _eventRepository.GetManyByProviderActingUserAsync(providerUser.ProviderId,
providerUser.UserId.Value, dateRange.Item1, dateRange.Item2, providerUser.UserId.Value, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken }); new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e)); var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken); return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
} }
private Tuple<DateTime, DateTime> 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<DateTime, DateTime>(start.Value, end.Value);
}
} }

View File

@ -78,7 +78,12 @@ public class OrganizationConnectionsController : Controller
[HttpPut("{organizationConnectionId}")] [HttpPut("{organizationConnectionId}")]
public async Task<OrganizationConnectionResponseModel> UpdateConnection(Guid organizationConnectionId, [FromBody] OrganizationConnectionRequestModel model) public async Task<OrganizationConnectionResponseModel> 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) if (existingOrganizationConnection == null)
{ {
throw new NotFoundException(); throw new NotFoundException();

View File

@ -19,7 +19,7 @@ public class OrganizationDomainController : Controller
private readonly ICreateOrganizationDomainCommand _createOrganizationDomainCommand; private readonly ICreateOrganizationDomainCommand _createOrganizationDomainCommand;
private readonly IVerifyOrganizationDomainCommand _verifyOrganizationDomainCommand; private readonly IVerifyOrganizationDomainCommand _verifyOrganizationDomainCommand;
private readonly IDeleteOrganizationDomainCommand _deleteOrganizationDomainCommand; private readonly IDeleteOrganizationDomainCommand _deleteOrganizationDomainCommand;
private readonly IGetOrganizationDomainByIdQuery _getOrganizationDomainByIdQuery; private readonly IGetOrganizationDomainByIdOrganizationIdQuery _getOrganizationDomainByIdAndOrganizationIdQuery;
private readonly IGetOrganizationDomainByOrganizationIdQuery _getOrganizationDomainByOrganizationIdQuery; private readonly IGetOrganizationDomainByOrganizationIdQuery _getOrganizationDomainByOrganizationIdQuery;
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationRepository _organizationRepository;
@ -29,7 +29,7 @@ public class OrganizationDomainController : Controller
ICreateOrganizationDomainCommand createOrganizationDomainCommand, ICreateOrganizationDomainCommand createOrganizationDomainCommand,
IVerifyOrganizationDomainCommand verifyOrganizationDomainCommand, IVerifyOrganizationDomainCommand verifyOrganizationDomainCommand,
IDeleteOrganizationDomainCommand deleteOrganizationDomainCommand, IDeleteOrganizationDomainCommand deleteOrganizationDomainCommand,
IGetOrganizationDomainByIdQuery getOrganizationDomainByIdQuery, IGetOrganizationDomainByIdOrganizationIdQuery getOrganizationDomainByIdAndOrganizationIdQuery,
IGetOrganizationDomainByOrganizationIdQuery getOrganizationDomainByOrganizationIdQuery, IGetOrganizationDomainByOrganizationIdQuery getOrganizationDomainByOrganizationIdQuery,
ICurrentContext currentContext, ICurrentContext currentContext,
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
@ -38,7 +38,7 @@ public class OrganizationDomainController : Controller
_createOrganizationDomainCommand = createOrganizationDomainCommand; _createOrganizationDomainCommand = createOrganizationDomainCommand;
_verifyOrganizationDomainCommand = verifyOrganizationDomainCommand; _verifyOrganizationDomainCommand = verifyOrganizationDomainCommand;
_deleteOrganizationDomainCommand = deleteOrganizationDomainCommand; _deleteOrganizationDomainCommand = deleteOrganizationDomainCommand;
_getOrganizationDomainByIdQuery = getOrganizationDomainByIdQuery; _getOrganizationDomainByIdAndOrganizationIdQuery = getOrganizationDomainByIdAndOrganizationIdQuery;
_getOrganizationDomainByOrganizationIdQuery = getOrganizationDomainByOrganizationIdQuery; _getOrganizationDomainByOrganizationIdQuery = getOrganizationDomainByOrganizationIdQuery;
_currentContext = currentContext; _currentContext = currentContext;
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
@ -46,71 +46,78 @@ public class OrganizationDomainController : Controller
} }
[HttpGet("{orgId}/domain")] [HttpGet("{orgId}/domain")]
public async Task<ListResponseModel<OrganizationDomainResponseModel>> Get(string orgId) public async Task<ListResponseModel<OrganizationDomainResponseModel>> Get(Guid orgId)
{ {
var orgIdGuid = new Guid(orgId); await ValidateOrganizationAccessAsync(orgId);
await ValidateOrganizationAccessAsync(orgIdGuid);
var domains = await _getOrganizationDomainByOrganizationIdQuery var domains = await _getOrganizationDomainByOrganizationIdQuery
.GetDomainsByOrganizationId(orgIdGuid); .GetDomainsByOrganizationIdAsync(orgId);
var response = domains.Select(x => new OrganizationDomainResponseModel(x)).ToList(); var response = domains.Select(x => new OrganizationDomainResponseModel(x)).ToList();
return new ListResponseModel<OrganizationDomainResponseModel>(response); return new ListResponseModel<OrganizationDomainResponseModel>(response);
} }
[HttpGet("{orgId}/domain/{id}")] [HttpGet("{orgId}/domain/{id}")]
public async Task<OrganizationDomainResponseModel> Get(string orgId, string id) public async Task<OrganizationDomainResponseModel> Get(Guid orgId, Guid id)
{ {
var orgIdGuid = new Guid(orgId); await ValidateOrganizationAccessAsync(orgId);
var IdGuid = new Guid(id);
await ValidateOrganizationAccessAsync(orgIdGuid);
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<OrganizationDomainResponseModel> 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<OrganizationDomainResponseModel> 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) if (domain is null)
{ {
throw new NotFoundException(); throw new NotFoundException();
} }
return new OrganizationDomainResponseModel(domain); await _deleteOrganizationDomainCommand.DeleteAsync(domain);
}
[HttpPost("{orgId}/domain")]
public async Task<OrganizationDomainResponseModel> 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<OrganizationDomainResponseModel> Verify(string orgId, string id)
{
var orgIdGuid = new Guid(orgId);
var idGuid = new Guid(id);
await ValidateOrganizationAccessAsync(orgIdGuid);
var domain = await _verifyOrganizationDomainCommand.VerifyOrganizationDomain(idGuid);
return new OrganizationDomainResponseModel(domain);
}
[HttpDelete("{orgId}/domain/{id}")]
[HttpPost("{orgId}/domain/{id}/remove")]
public async Task RemoveDomain(string orgId, string id)
{
var orgIdGuid = new Guid(orgId);
var idGuid = new Guid(id);
await ValidateOrganizationAccessAsync(orgIdGuid);
await _deleteOrganizationDomainCommand.DeleteAsync(idGuid);
} }
[AllowAnonymous] [AllowAnonymous]

View File

@ -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.Request.Organizations;
using Bit.Api.Models.Response.Organizations;
using Bit.Api.Utilities; using Bit.Api.Utilities;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;

View File

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Api.Models.Request.Organizations; namespace Bit.Api.Models.Request.Organizations;

View File

@ -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.Entities;
using Bit.Core.Models.Api; using Bit.Core.Models.Api;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;

View File

@ -13,7 +13,6 @@ public class SubscriptionResponseModel : ResponseModel
Subscription = subscription.Subscription != null ? new BillingSubscription(subscription.Subscription) : null; Subscription = subscription.Subscription != null ? new BillingSubscription(subscription.Subscription) : null;
UpcomingInvoice = subscription.UpcomingInvoice != null ? UpcomingInvoice = subscription.UpcomingInvoice != null ?
new BillingSubscriptionUpcomingInvoice(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; StorageName = user.Storage.HasValue ? CoreHelpers.ReadableBytesSize(user.Storage.Value) : null;
StorageGb = user.Storage.HasValue ? Math.Round(user.Storage.Value / 1073741824D, 2) : 0; // 1 GB StorageGb = user.Storage.HasValue ? Math.Round(user.Storage.Value / 1073741824D, 2) : 0; // 1 GB
MaxStorageGb = user.MaxStorageGb; MaxStorageGb = user.MaxStorageGb;
@ -41,7 +40,6 @@ public class SubscriptionResponseModel : ResponseModel
public short? MaxStorageGb { get; set; } public short? MaxStorageGb { get; set; }
public BillingSubscriptionUpcomingInvoice UpcomingInvoice { get; set; } public BillingSubscriptionUpcomingInvoice UpcomingInvoice { get; set; }
public BillingSubscription Subscription { get; set; } public BillingSubscription Subscription { get; set; }
public BillingCustomerDiscount Discount { get; set; }
public UserLicense License { get; set; } public UserLicense License { get; set; }
public DateTime? Expiration { get; set; } public DateTime? Expiration { get; set; }
public bool UsingInAppPurchase { get; set; } public bool UsingInAppPurchase { get; set; }
@ -53,10 +51,12 @@ public class BillingCustomerDiscount
{ {
Id = discount.Id; Id = discount.Id;
Active = discount.Active; Active = discount.Active;
PercentOff = discount.PercentOff;
} }
public string Id { get; set; } public string Id { get; }
public bool Active { get; set; } public bool Active { get; }
public decimal? PercentOff { get; }
} }
public class BillingSubscription public class BillingSubscription

View File

@ -1,6 +1,7 @@
using Bit.Api.Models.Response; using Bit.Api.Models.Response;
using Bit.Api.SecretsManager.Models.Request; using Bit.Api.SecretsManager.Models.Request;
using Bit.Api.SecretsManager.Models.Response; using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;

View File

@ -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<ListResponseModel<EventResponseModel>> 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<EventResponseModel>(responses, result.ContinuationToken);
}
}

View File

@ -1,4 +1,4 @@
using Bit.Core.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.Models.Api; using Bit.Core.Models.Api;
using Bit.Core.Models.Data.Organizations.OrganizationUsers; using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;

View File

@ -1,6 +1,7 @@
using System.Text.Json; using System.Text.Json;
using Azure.Messaging.EventGrid; using Azure.Messaging.EventGrid;
using Azure.Messaging.EventGrid.SystemEvents; using Azure.Messaging.EventGrid.SystemEvents;
using Bit.Core.Exceptions;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -69,4 +70,35 @@ public static class ApiHelpers
return new OkObjectResult(response); return new OkObjectResult(response);
} }
/// <summary>
/// Validates and returns a date range. Currently used for fetching events.
/// </summary>
/// <param name="start">start date and time</param>
/// <param name="end">end date and time</param>
/// <remarks>
/// 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.
/// </remarks>
public static Tuple<DateTime, DateTime> 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<DateTime, DateTime>(start.Value, end.Value);
}
} }

View File

@ -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.Settings;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Core.Vault.Entities; using Bit.Core.Vault.Entities;
@ -15,7 +14,7 @@ public class AttachmentResponseModel : ResponseModel
Url = data.Url; Url = data.Url;
FileName = data.Data.FileName; FileName = data.Data.FileName;
Key = data.Data.Key; Key = data.Data.Key;
Size = data.Data.Size; Size = data.Data.Size.ToString();
SizeName = CoreHelpers.ReadableBytesSize(data.Data.Size); SizeName = CoreHelpers.ReadableBytesSize(data.Data.Size);
} }
@ -27,7 +26,7 @@ public class AttachmentResponseModel : ResponseModel
Url = $"{globalSettings.Attachment.BaseUrl}/{cipher.Id}/{id}"; Url = $"{globalSettings.Attachment.BaseUrl}/{cipher.Id}/{id}";
FileName = data.FileName; FileName = data.FileName;
Key = data.Key; Key = data.Key;
Size = data.Size; Size = data.Size.ToString();
SizeName = CoreHelpers.ReadableBytesSize(data.Size); SizeName = CoreHelpers.ReadableBytesSize(data.Size);
} }
@ -35,8 +34,7 @@ public class AttachmentResponseModel : ResponseModel
public string Url { get; set; } public string Url { get; set; }
public string FileName { get; set; } public string FileName { get; set; }
public string Key { get; set; } public string Key { get; set; }
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)] public string Size { get; set; }
public long Size { get; set; }
public string SizeName { get; set; } public string SizeName { get; set; }
public static IEnumerable<AttachmentResponseModel> FromCipher(Cipher cipher, IGlobalSettings globalSettings) public static IEnumerable<AttachmentResponseModel> FromCipher(Cipher cipher, IGlobalSettings globalSettings)

View File

@ -52,6 +52,7 @@ public class StripeController : Controller
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private readonly IStripeEventService _stripeEventService; private readonly IStripeEventService _stripeEventService;
private readonly IStripeFacade _stripeFacade;
public StripeController( public StripeController(
GlobalSettings globalSettings, GlobalSettings globalSettings,
@ -70,7 +71,8 @@ public class StripeController : Controller
ITaxRateRepository taxRateRepository, ITaxRateRepository taxRateRepository,
IUserRepository userRepository, IUserRepository userRepository,
ICurrentContext currentContext, ICurrentContext currentContext,
IStripeEventService stripeEventService) IStripeEventService stripeEventService,
IStripeFacade stripeFacade)
{ {
_billingSettings = billingSettings?.Value; _billingSettings = billingSettings?.Value;
_hostingEnvironment = hostingEnvironment; _hostingEnvironment = hostingEnvironment;
@ -97,6 +99,7 @@ public class StripeController : Controller
_currentContext = currentContext; _currentContext = currentContext;
_globalSettings = globalSettings; _globalSettings = globalSettings;
_stripeEventService = stripeEventService; _stripeEventService = stripeEventService;
_stripeFacade = stripeFacade;
} }
[HttpPost("webhook")] [HttpPost("webhook")]
@ -209,48 +212,71 @@ public class StripeController : Controller
else if (parsedEvent.Type.Equals(HandledStripeWebhook.UpcomingInvoice)) else if (parsedEvent.Type.Equals(HandledStripeWebhook.UpcomingInvoice))
{ {
var invoice = await _stripeEventService.GetInvoice(parsedEvent); 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) 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 (organizationId, userId) = GetIdsFromMetaData(updatedSubscription.Metadata);
var ids = GetIdsFromMetaData(subscription.Metadata);
// org var invoiceLineItemDescriptions = invoice.Lines.Select(i => i.Description).ToList();
if (ids.Item1.HasValue)
async Task SendEmails(IEnumerable<string> emails)
{ {
// sponsored org var validEmails = emails.Where(e => !string.IsNullOrEmpty(e));
if (IsSponsoredSubscription(subscription))
if (invoice.NextPaymentAttempt.HasValue)
{ {
await _validateSponsorshipCommand.ValidateSponsorshipAsync(ids.Item1.Value); await _mailService.SendInvoiceUpcoming(
validEmails,
invoice.AmountDue / 100M,
invoice.NextPaymentAttempt.Value,
invoiceLineItemDescriptions,
true);
}
} }
var org = await _organizationRepository.GetByIdAsync(ids.Item1.Value); if (organizationId.HasValue)
if (org != null && OrgPlanForInvoiceNotifications(org))
{ {
email = org.BillingEmail; if (IsSponsoredSubscription(updatedSubscription))
}
}
// user
else if (ids.Item2.HasValue)
{ {
var user = await _userService.GetUserByIdAsync(ids.Item2.Value); await _validateSponsorshipCommand.ValidateSponsorshipAsync(organizationId.Value);
}
var organization = await _organizationRepository.GetByIdAsync(organizationId.Value);
if (organization == null || !OrgPlanForInvoiceNotifications(organization))
{
return new OkResult();
}
await SendEmails(new List<string> { 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) if (user.Premium)
{ {
email = user.Email; await SendEmails(new List<string> { 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)) else if (parsedEvent.Type.Equals(HandledStripeWebhook.ChargeSucceeded))
{ {

View File

@ -7,13 +7,16 @@ namespace Bit.Billing.Services.Implementations;
public class StripeEventService : IStripeEventService public class StripeEventService : IStripeEventService
{ {
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private readonly ILogger<StripeEventService> _logger;
private readonly IStripeFacade _stripeFacade; private readonly IStripeFacade _stripeFacade;
public StripeEventService( public StripeEventService(
GlobalSettings globalSettings, GlobalSettings globalSettings,
ILogger<StripeEventService> logger,
IStripeFacade stripeFacade) IStripeFacade stripeFacade)
{ {
_globalSettings = globalSettings; _globalSettings = globalSettings;
_logger = logger;
_stripeFacade = stripeFacade; _stripeFacade = stripeFacade;
} }
@ -26,6 +29,12 @@ public class StripeEventService : IStripeEventService
return eventCharge; 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 }); var charge = await _stripeFacade.GetCharge(eventCharge.Id, new ChargeGetOptions { Expand = expand });
if (charge == null) if (charge == null)
@ -46,6 +55,12 @@ public class StripeEventService : IStripeEventService
return eventCustomer; 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 }); var customer = await _stripeFacade.GetCustomer(eventCustomer.Id, new CustomerGetOptions { Expand = expand });
if (customer == null) if (customer == null)
@ -66,6 +81,12 @@ public class StripeEventService : IStripeEventService
return eventInvoice; 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 }); var invoice = await _stripeFacade.GetInvoice(eventInvoice.Id, new InvoiceGetOptions { Expand = expand });
if (invoice == null) if (invoice == null)
@ -86,6 +107,12 @@ public class StripeEventService : IStripeEventService
return eventPaymentMethod; 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 }); var paymentMethod = await _stripeFacade.GetPaymentMethod(eventPaymentMethod.Id, new PaymentMethodGetOptions { Expand = expand });
if (paymentMethod == null) if (paymentMethod == null)
@ -106,6 +133,12 @@ public class StripeEventService : IStripeEventService
return eventSubscription; 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 }); var subscription = await _stripeFacade.GetSubscription(eventSubscription.Id, new SubscriptionGetOptions { Expand = expand });
if (subscription == null) if (subscription == null)
@ -132,7 +165,7 @@ public class StripeEventService : IStripeEventService
(await GetCharge(stripeEvent, true, customerExpansion))?.Customer?.Metadata, (await GetCharge(stripeEvent, true, customerExpansion))?.Customer?.Metadata,
HandledStripeWebhook.UpcomingInvoice => HandledStripeWebhook.UpcomingInvoice =>
(await GetInvoice(stripeEvent, true, customerExpansion))?.Customer?.Metadata, await GetCustomerMetadataFromUpcomingInvoiceEvent(stripeEvent),
HandledStripeWebhook.PaymentSucceeded or HandledStripeWebhook.PaymentFailed or HandledStripeWebhook.InvoiceCreated => HandledStripeWebhook.PaymentSucceeded or HandledStripeWebhook.PaymentFailed or HandledStripeWebhook.InvoiceCreated =>
(await GetInvoice(stripeEvent, true, customerExpansion))?.Customer?.Metadata, (await GetInvoice(stripeEvent, true, customerExpansion))?.Customer?.Metadata,
@ -154,6 +187,20 @@ public class StripeEventService : IStripeEventService
var customerRegion = GetCustomerRegion(customerMetadata); var customerRegion = GetCustomerRegion(customerMetadata);
return customerRegion == serverRegion; 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<Dictionary<string, string>> 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<T>(Event stripeEvent) private static T Extract<T>(Event stripeEvent)

View File

@ -10,18 +10,22 @@ public class PayPalIpnClient
{ {
private readonly HttpClient _httpClient = new HttpClient(); private readonly HttpClient _httpClient = new HttpClient();
private readonly Uri _ipnUri; private readonly Uri _ipnUri;
private readonly ILogger<PayPalIpnClient> _logger;
public PayPalIpnClient(IOptions<BillingSettings> billingSettings) public PayPalIpnClient(IOptions<BillingSettings> billingSettings, ILogger<PayPalIpnClient> logger)
{ {
var bSettings = billingSettings?.Value; var bSettings = billingSettings?.Value;
_ipnUri = new Uri(bSettings.PayPal.Production ? "https://www.paypal.com/cgi-bin/webscr" : _ipnUri = new Uri(bSettings.PayPal.Production ? "https://ipnpb.paypal.com/cgi-bin/webscr" :
"https://www.sandbox.paypal.com/cgi-bin/webscr"); "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr");
_logger = logger;
} }
public async Task<bool> VerifyIpnAsync(string ipnBody) public async Task<bool> VerifyIpnAsync(string ipnBody)
{ {
_logger.LogInformation("Verifying IPN with PayPal at {Timestamp}: {VerificationUri}", DateTime.UtcNow, _ipnUri);
if (ipnBody == null) if (ipnBody == null)
{ {
_logger.LogError("No IPN body.");
throw new ArgumentException("No IPN body."); throw new ArgumentException("No IPN body.");
} }
@ -30,6 +34,7 @@ public class PayPalIpnClient
Method = HttpMethod.Post, Method = HttpMethod.Post,
RequestUri = _ipnUri RequestUri = _ipnUri
}; };
_httpClient.DefaultRequestHeaders.Add("User-Agent", "CSharp-IPN-VerificationScript");
var cmdIpnBody = string.Concat("cmd=_notify-validate&", ipnBody); var cmdIpnBody = string.Concat("cmd=_notify-validate&", ipnBody);
request.Content = new StringContent(cmdIpnBody, Encoding.UTF8, "application/x-www-form-urlencoded"); request.Content = new StringContent(cmdIpnBody, Encoding.UTF8, "application/x-www-form-urlencoded");
var response = await _httpClient.SendAsync(request); var response = await _httpClient.SendAsync(request);
@ -42,15 +47,15 @@ public class PayPalIpnClient
{ {
return true; return true;
} }
else if (responseContent.Equals("INVALID"))
if (responseContent.Equals("INVALID"))
{ {
_logger.LogWarning("Received an INVALID response from PayPal: {ResponseContent}", responseContent);
return false; return false;
} }
else _logger.LogError("Failed to verify IPN: {ResponseContent}", responseContent);
{
throw new Exception("Failed to verify IPN."); throw new Exception("Failed to verify IPN.");
} }
}
public class IpnTransaction public class IpnTransaction
{ {

View File

@ -1,8 +1,9 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Bit.Core.Entities;
using Bit.Core.Models; using Bit.Core.Models;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Core.Entities; namespace Bit.Core.AdminConsole.Entities;
public class Group : ITableObject<Guid>, IExternal public class Group : ITableObject<Guid>, IExternal
{ {

View File

@ -1,4 +1,4 @@
namespace Bit.Core.Entities; namespace Bit.Core.AdminConsole.Entities;
public class GroupUser public class GroupUser
{ {

View File

@ -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 public class ImportedGroup
{ {

Some files were not shown because too many files have changed in this diff Show More