1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-02 16:42:50 -05:00

Merge branch 'master' into feature/billing-obfuscation

This commit is contained in:
Rui Tome
2023-02-03 10:15:41 +00:00
154 changed files with 17081 additions and 1370 deletions

View File

@ -38,7 +38,7 @@ jobs:
testing: testing:
name: Testing name: Testing
runs-on: windows-2022 runs-on: ubuntu-22.04
env: env:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps: steps:
@ -46,13 +46,10 @@ jobs:
uses: actions/setup-dotnet@9211491ffb35dd6a6657ca4f45d43dfe6e97c829 uses: actions/setup-dotnet@9211491ffb35dd6a6657ca4f45d43dfe6e97c829
with: with:
dotnet-version: "6.0.x" dotnet-version: "6.0.x"
- name: Set up MSBuild
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
- name: Print environment - name: Print environment
run: | run: |
dotnet --info dotnet --info
msbuild -version
nuget help | grep Version nuget help | grep Version
echo "GitHub ref: $GITHUB_REF" echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
@ -64,20 +61,23 @@ jobs:
run: dotnet restore --locked-mode run: dotnet restore --locked-mode
shell: pwsh shell: pwsh
- name: Remove SQL proj
run: dotnet sln bitwarden-server.sln remove src/Sql/Sql.sqlproj
- name: Build OSS solution - name: Build OSS solution
run: msbuild bitwarden-server.sln /p:Configuration=Debug /p:DefineConstants="OSS" /verbosity:minimal run: dotnet build bitwarden-server.sln -p:Configuration=Debug -p:DefineConstants="OSS" --verbosity minimal
shell: pwsh shell: pwsh
- name: Build solution - name: Build solution
run: msbuild bitwarden-server.sln /p:Configuration=Debug /verbosity:minimal run: dotnet build bitwarden-server.sln -p:Configuration=Debug --verbosity minimal
shell: pwsh shell: pwsh
- name: Test OSS solution - name: Test OSS solution
run: dotnet test ./test --configuration Debug --no-build --logger "trx;LogFileName=oss-test-results.trx" || true run: dotnet test ./test --configuration Debug --no-build --logger "trx;LogFileName=oss-test-results.trx"
shell: pwsh shell: pwsh
- name: Test Bitwarden solution - name: Test Bitwarden solution
run: dotnet test ./bitwarden_license/test --configuration Debug --no-build --logger "trx;LogFileName=bw-test-results.trx" || true run: dotnet test ./bitwarden_license/test --configuration Debug --no-build --logger "trx;LogFileName=bw-test-results.trx"
shell: pwsh shell: pwsh
- name: Report test results - name: Report test results
@ -250,6 +250,14 @@ jobs:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
- name: Set up image tag
run: |
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name
if [[ "$IMAGE_TAG" == "master" ]]; then
IMAGE_TAG=dev
fi
echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV
########## Build Docker Image ########## ########## Build Docker Image ##########
- name: Setup project name - name: Setup project name
id: setup id: setup
@ -277,28 +285,44 @@ jobs:
PROJECT_NAME: ${{ steps.setup.outputs.project_name }} PROJECT_NAME: ${{ steps.setup.outputs.project_name }}
run: docker build -t $PROJECT_NAME ${{ matrix.base_path }}/${{ matrix.project_name }} run: docker build -t $PROJECT_NAME ${{ matrix.base_path }}/${{ matrix.project_name }}
########## ACR ########## ########## QA ACR ##########
- name: Login to Azure - QA Subscription - name: Login to Azure - QA Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
with: with:
creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }} creds: ${{ secrets.AZURE_QA_KV_CREDENTIALS }}
- name: Login to Azure ACR - name: Login to QA ACR
run: az acr login -n bitwardenqa run: az acr login -n bitwardenqa
- name: Tag and Push image to Azure ACR QA registry - name: Tag and push image to QA ACR
env: env:
PROJECT_NAME: ${{ steps.setup.outputs.project_name }} PROJECT_NAME: ${{ steps.setup.outputs.project_name }}
REGISTRY: bitwardenqa.azurecr.io REGISTRY: bitwardenqa.azurecr.io
run: | run: |
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name
if [[ "$IMAGE_TAG" == "master" ]]; then
IMAGE_TAG=dev
fi
docker tag $PROJECT_NAME \ docker tag $PROJECT_NAME \
$REGISTRY/$PROJECT_NAME:$IMAGE_TAG $REGISTRY/$PROJECT_NAME:${{ env.IMAGE_TAG }}
docker push $REGISTRY/$PROJECT_NAME:$IMAGE_TAG docker push $REGISTRY/$PROJECT_NAME:${{ env.IMAGE_TAG }}
- name: Log out of Docker
run: docker logout
########## PROD ACR ##########
- name: Login to Azure - PROD Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Login to PROD ACR
run: az acr login -n bitwardenprod
- name: Tag and push image to PROD ACR
env:
PROJECT_NAME: ${{ steps.setup.outputs.project_name }}
REGISTRY: bitwardenprod.azurecr.io
run: |
docker tag $PROJECT_NAME \
$REGISTRY/$PROJECT_NAME:${{ env.IMAGE_TAG }}
docker push $REGISTRY/$PROJECT_NAME:${{ env.IMAGE_TAG }}
- name: Log out of Docker - name: Log out of Docker
run: docker logout run: docker logout
@ -366,14 +390,9 @@ jobs:
PROJECT_NAME: ${{ steps.setup.outputs.project_name }} PROJECT_NAME: ${{ steps.setup.outputs.project_name }}
REGISTRY: bitwarden REGISTRY: bitwarden
run: | run: |
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g") # slash safe branch name
if [[ "$IMAGE_TAG" == "master" ]]; then
IMAGE_TAG=dev
fi
docker tag $PROJECT_NAME \ docker tag $PROJECT_NAME \
$REGISTRY/$PROJECT_NAME:$IMAGE_TAG $REGISTRY/$PROJECT_NAME:${{ env.IMAGE_TAG }}
docker push $REGISTRY/$PROJECT_NAME:$IMAGE_TAG docker push $REGISTRY/$PROJECT_NAME:${{ env.IMAGE_TAG }}
- name: Log out of Docker and disable Docker Notary - name: Log out of Docker and disable Docker Notary
if: | if: |

View File

@ -0,0 +1,11 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Full Server - Self-hosted" type="CompoundRunConfigurationType">
<toRun name="Admin: Admin-SelfHost" type="LaunchSettings" />
<toRun name="Api: Api-SelfHost" type="LaunchSettings" />
<toRun name="Events: Events-SelfHost" type="LaunchSettings" />
<toRun name="Identity: Identity-SelfHost" type="LaunchSettings" />
<toRun name="Notifications: Notifications-SelfHost" type="LaunchSettings" />
<toRun name="Sso: Sso-SelfHost" type="LaunchSettings" />
<method v="2" />
</configuration>
</component>

14
.run/Full Server.run.xml Normal file
View File

@ -0,0 +1,14 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Full Server" type="CompoundRunConfigurationType">
<toRun name="Admin" type="LaunchSettings" />
<toRun name="Api" type="LaunchSettings" />
<toRun name="Billing" type="LaunchSettings" />
<toRun name="Events" type="LaunchSettings" />
<toRun name="EventsProcessor" type="LaunchSettings" />
<toRun name="Icons" type="LaunchSettings" />
<toRun name="Identity" type="LaunchSettings" />
<toRun name="Notifications" type="LaunchSettings" />
<toRun name="Sso" type="LaunchSettings" />
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Min Server - Self-hosted" type="CompoundRunConfigurationType">
<toRun name="Api: Api-SelfHost" type="LaunchSettings" />
<toRun name="Identity: Identity-SelfHost" type="LaunchSettings" />
<method v="2" />
</configuration>
</component>

7
.run/Min Server.run.xml Normal file
View File

@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Min Server" type="CompoundRunConfigurationType">
<toRun name="Api" type="LaunchSettings" />
<toRun name="Identity" type="LaunchSettings" />
<method v="2" />
</configuration>
</component>

View File

@ -33,6 +33,12 @@ public class CreateAccessTokenCommand : ICreateAccessTokenCommand
} }
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(apiKey.ServiceAccountId.Value); var serviceAccount = await _serviceAccountRepository.GetByIdAsync(apiKey.ServiceAccountId.Value);
if (!_currentContext.AccessSecretsManager(serviceAccount.OrganizationId))
{
throw new NotFoundException();
}
var orgAdmin = await _currentContext.OrganizationAdmin(serviceAccount.OrganizationId); var orgAdmin = await _currentContext.OrganizationAdmin(serviceAccount.OrganizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
@ -46,7 +52,7 @@ public class CreateAccessTokenCommand : ICreateAccessTokenCommand
if (!hasAccess) if (!hasAccess)
{ {
throw new UnauthorizedAccessException(); throw new NotFoundException();
} }
apiKey.ClientSecret = CoreHelpers.SecureRandomString(_clientSecretMaxLength); apiKey.ClientSecret = CoreHelpers.SecureRandomString(_clientSecretMaxLength);

View File

@ -36,7 +36,12 @@ public class DeleteProjectCommand : IDeleteProjectCommand
var organizationId = projects.First().OrganizationId; var organizationId = projects.First().OrganizationId;
if (projects.Any(p => p.OrganizationId != organizationId)) if (projects.Any(p => p.OrganizationId != organizationId))
{ {
throw new UnauthorizedAccessException(); throw new BadRequestException();
}
if (!_currentContext.AccessSecretsManager(organizationId))
{
throw new NotFoundException();
} }
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId); var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);

View File

@ -26,6 +26,11 @@ public class UpdateProjectCommand : IUpdateProjectCommand
throw new NotFoundException(); throw new NotFoundException();
} }
if (!_currentContext.AccessSecretsManager(project.OrganizationId))
{
throw new NotFoundException();
}
var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId); var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
@ -38,7 +43,7 @@ public class UpdateProjectCommand : IUpdateProjectCommand
if (!hasAccess) if (!hasAccess)
{ {
throw new UnauthorizedAccessException(); throw new NotFoundException();
} }
project.Name = updatedProject.Name; project.Name = updatedProject.Name;

View File

@ -1,4 +1,5 @@
using Bit.Core.Exceptions; using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces; using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
@ -7,10 +8,12 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.Secrets;
public class DeleteSecretCommand : IDeleteSecretCommand public class DeleteSecretCommand : IDeleteSecretCommand
{ {
private readonly ICurrentContext _currentContext;
private readonly ISecretRepository _secretRepository; private readonly ISecretRepository _secretRepository;
public DeleteSecretCommand(ISecretRepository secretRepository) public DeleteSecretCommand(ICurrentContext currentContext, ISecretRepository secretRepository)
{ {
_currentContext = currentContext;
_secretRepository = secretRepository; _secretRepository = secretRepository;
} }
@ -23,6 +26,18 @@ public class DeleteSecretCommand : IDeleteSecretCommand
throw new NotFoundException(); throw new NotFoundException();
} }
// Ensure all secrets belongs to the same organization
var organizationId = secrets.First().OrganizationId;
if (secrets.Any(p => p.OrganizationId != organizationId))
{
throw new BadRequestException();
}
if (!_currentContext.AccessSecretsManager(organizationId))
{
throw new NotFoundException();
}
var results = ids.Select(id => var results = ids.Select(id =>
{ {
var secret = secrets.FirstOrDefault(secret => secret.Id == id); var secret = secrets.FirstOrDefault(secret => secret.Id == id);

View File

@ -1,4 +1,5 @@
using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces; using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
@ -6,15 +7,34 @@ namespace Bit.Commercial.Core.SecretsManager.Commands.ServiceAccounts;
public class CreateServiceAccountCommand : ICreateServiceAccountCommand public class CreateServiceAccountCommand : ICreateServiceAccountCommand
{ {
private readonly IAccessPolicyRepository _accessPolicyRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IServiceAccountRepository _serviceAccountRepository; private readonly IServiceAccountRepository _serviceAccountRepository;
public CreateServiceAccountCommand(IServiceAccountRepository serviceAccountRepository) public CreateServiceAccountCommand(
IAccessPolicyRepository accessPolicyRepository,
IOrganizationUserRepository organizationUserRepository,
IServiceAccountRepository serviceAccountRepository)
{ {
_accessPolicyRepository = accessPolicyRepository;
_organizationUserRepository = organizationUserRepository;
_serviceAccountRepository = serviceAccountRepository; _serviceAccountRepository = serviceAccountRepository;
} }
public async Task<ServiceAccount> CreateAsync(ServiceAccount serviceAccount) public async Task<ServiceAccount> CreateAsync(ServiceAccount serviceAccount, Guid userId)
{ {
return await _serviceAccountRepository.CreateAsync(serviceAccount); var createdServiceAccount = await _serviceAccountRepository.CreateAsync(serviceAccount);
var user = await _organizationUserRepository.GetByOrganizationAsync(createdServiceAccount.OrganizationId,
userId);
var accessPolicy = new UserServiceAccountAccessPolicy
{
OrganizationUserId = user.Id,
GrantedServiceAccountId = createdServiceAccount.Id,
Read = true,
Write = true,
};
await _accessPolicyRepository.CreateManyAsync(new List<BaseAccessPolicy> { accessPolicy });
return createdServiceAccount;
} }
} }

View File

@ -26,6 +26,11 @@ public class UpdateServiceAccountCommand : IUpdateServiceAccountCommand
throw new NotFoundException(); throw new NotFoundException();
} }
if (!_currentContext.AccessSecretsManager(serviceAccount.OrganizationId))
{
throw new NotFoundException();
}
var orgAdmin = await _currentContext.OrganizationAdmin(serviceAccount.OrganizationId); var orgAdmin = await _currentContext.OrganizationAdmin(serviceAccount.OrganizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
@ -38,7 +43,7 @@ public class UpdateServiceAccountCommand : IUpdateServiceAccountCommand
if (!hasAccess) if (!hasAccess)
{ {
throw new UnauthorizedAccessException(); throw new NotFoundException();
} }
serviceAccount.Name = updatedServiceAccount.Name; serviceAccount.Name = updatedServiceAccount.Name;

View File

@ -138,7 +138,7 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
} }
} }
public async Task<IEnumerable<Core.SecretsManager.Entities.BaseAccessPolicy>?> GetManyByProjectId(Guid id) public async Task<IEnumerable<Core.SecretsManager.Entities.BaseAccessPolicy>> GetManyByGrantedProjectIdAsync(Guid id)
{ {
using (var scope = ServiceScopeFactory.CreateScope()) using (var scope = ServiceScopeFactory.CreateScope())
{ {
@ -153,10 +153,25 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
.Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount) .Include(ap => ((ServiceAccountProjectAccessPolicy)ap).ServiceAccount)
.ToListAsync(); .ToListAsync();
return !entities.Any() ? null : entities.Select(MapToCore); return entities.Select(MapToCore);
} }
} }
public async Task<IEnumerable<Core.SecretsManager.Entities.BaseAccessPolicy>> GetManyByGrantedServiceAccountIdAsync(Guid id)
{
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var entities = await dbContext.AccessPolicies.Where(ap =>
((UserServiceAccountAccessPolicy)ap).GrantedServiceAccountId == id ||
((GroupServiceAccountAccessPolicy)ap).GrantedServiceAccountId == id)
.Include(ap => ((UserServiceAccountAccessPolicy)ap).OrganizationUser.User)
.Include(ap => ((GroupServiceAccountAccessPolicy)ap).Group)
.ToListAsync();
return entities.Select(MapToCore);
}
public async Task DeleteAsync(Guid id) public async Task DeleteAsync(Guid id)
{ {
using (var scope = ServiceScopeFactory.CreateScope()) using (var scope = ServiceScopeFactory.CreateScope())
@ -178,6 +193,8 @@ public class AccessPolicyRepository : BaseEntityFrameworkRepository, IAccessPoli
UserProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.UserProjectAccessPolicy>(ap), UserProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.UserProjectAccessPolicy>(ap),
GroupProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.GroupProjectAccessPolicy>(ap), GroupProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.GroupProjectAccessPolicy>(ap),
ServiceAccountProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy>(ap), ServiceAccountProjectAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.ServiceAccountProjectAccessPolicy>(ap),
UserServiceAccountAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.UserServiceAccountAccessPolicy>(ap),
GroupServiceAccountAccessPolicy ap => Mapper.Map<Core.SecretsManager.Entities.GroupServiceAccountAccessPolicy>(ap),
_ => throw new ArgumentException("Unsupported access policy type") _ => throw new ArgumentException("Unsupported access policy type")
}; };
} }

View File

@ -36,7 +36,7 @@ public class CreateServiceAccountCommandTests
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(saData.Id).Returns(saData); sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(saData.Id).Returns(saData);
sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(saData.Id, userId).Returns(false); sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(saData.Id, userId).Returns(false);
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => sutProvider.Sut.CreateAsync(data, userId)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CreateAsync(data, userId));
await sutProvider.GetDependency<IApiKeyRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default); await sutProvider.GetDependency<IApiKeyRepository>().DidNotReceiveWithAnyArgs().CreateAsync(default);
} }
@ -49,6 +49,7 @@ public class CreateServiceAccountCommandTests
data.ServiceAccountId = saData.Id; data.ServiceAccountId = saData.Id;
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(saData.Id).Returns(saData); sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(saData.Id).Returns(saData);
sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(saData.Id, userId).Returns(true); sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(saData.Id, userId).Returns(true);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(saData.OrganizationId).Returns(true);
await sutProvider.Sut.CreateAsync(data, userId); await sutProvider.Sut.CreateAsync(data, userId);
@ -64,6 +65,7 @@ public class CreateServiceAccountCommandTests
data.ServiceAccountId = saData.Id; data.ServiceAccountId = saData.Id;
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(saData.Id).Returns(saData); sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(saData.Id).Returns(saData);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(saData.OrganizationId).Returns(true);
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(saData.OrganizationId).Returns(true); sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(saData.OrganizationId).Returns(true);
await sutProvider.Sut.CreateAsync(data, userId); await sutProvider.Sut.CreateAsync(data, userId);

View File

@ -28,7 +28,7 @@ public class DeleteProjectCommandTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task DeleteSecrets_OneIdNotFound_Throws_NotFoundException(List<Guid> data, Guid userId, public async Task Delete_OneIdNotFound_Throws_NotFoundException(List<Guid> data, Guid userId,
SutProvider<DeleteProjectCommand> sutProvider) SutProvider<DeleteProjectCommand> sutProvider)
{ {
var project = new Project() var project = new Project()
@ -49,6 +49,7 @@ public class DeleteProjectCommandTests
{ {
var projects = data.Select(id => new Project { Id = id, OrganizationId = organizationId }).ToList(); var projects = data.Select(id => new Project { Id = id, OrganizationId = organizationId }).ToList();
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId).Returns(true);
sutProvider.GetDependency<ICurrentContext>().ClientType = ClientType.User; sutProvider.GetDependency<ICurrentContext>().ClientType = ClientType.User;
sutProvider.GetDependency<IProjectRepository>().GetManyByIds(data).Returns(projects); sutProvider.GetDependency<IProjectRepository>().GetManyByIds(data).Returns(projects);
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(Arg.Any<Guid>(), userId).Returns(true); sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(Arg.Any<Guid>(), userId).Returns(true);
@ -65,11 +66,12 @@ public class DeleteProjectCommandTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task DeleteSecrets_User_No_Permission(List<Guid> data, Guid userId, Guid organizationId, public async Task Delete_User_No_Permission(List<Guid> data, Guid userId, Guid organizationId,
SutProvider<DeleteProjectCommand> sutProvider) SutProvider<DeleteProjectCommand> sutProvider)
{ {
var projects = data.Select(id => new Project { Id = id, OrganizationId = organizationId }).ToList(); var projects = data.Select(id => new Project { Id = id, OrganizationId = organizationId }).ToList();
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId).Returns(true);
sutProvider.GetDependency<ICurrentContext>().ClientType = ClientType.User; sutProvider.GetDependency<ICurrentContext>().ClientType = ClientType.User;
sutProvider.GetDependency<IProjectRepository>().GetManyByIds(data).Returns(projects); sutProvider.GetDependency<IProjectRepository>().GetManyByIds(data).Returns(projects);
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(userId, userId).Returns(false); sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(userId, userId).Returns(false);
@ -86,11 +88,12 @@ public class DeleteProjectCommandTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task DeleteSecrets_OrganizationAdmin_Success(List<Guid> data, Guid userId, Guid organizationId, public async Task Delete_OrganizationAdmin_Success(List<Guid> data, Guid userId, Guid organizationId,
SutProvider<DeleteProjectCommand> sutProvider) SutProvider<DeleteProjectCommand> sutProvider)
{ {
var projects = data.Select(id => new Project { Id = id, OrganizationId = organizationId }).ToList(); var projects = data.Select(id => new Project { Id = id, OrganizationId = organizationId }).ToList();
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(organizationId).Returns(true);
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(true); sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(organizationId).Returns(true);
sutProvider.GetDependency<IProjectRepository>().GetManyByIds(data).Returns(projects); sutProvider.GetDependency<IProjectRepository>().GetManyByIds(data).Returns(projects);

View File

@ -33,6 +33,7 @@ public class UpdateProjectCommandTests
public async Task UpdateAsync_Admin_Succeeds(Project project, Guid userId, SutProvider<UpdateProjectCommand> sutProvider) public async Task UpdateAsync_Admin_Succeeds(Project project, Guid userId, SutProvider<UpdateProjectCommand> sutProvider)
{ {
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).Returns(project); sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).Returns(project);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId).Returns(true);
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(project.OrganizationId).Returns(true); sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(project.OrganizationId).Returns(true);
var project2 = new Project { Id = project.Id, Name = "newName" }; var project2 = new Project { Id = project.Id, Name = "newName" };
@ -51,8 +52,9 @@ public class UpdateProjectCommandTests
{ {
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).Returns(project); sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).Returns(project);
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(project.Id, userId).Returns(false); sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(project.Id, userId).Returns(false);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId).Returns(true);
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => sutProvider.Sut.UpdateAsync(project, userId)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(project, userId));
await sutProvider.GetDependency<IProjectRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default); await sutProvider.GetDependency<IProjectRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
} }
@ -63,6 +65,7 @@ public class UpdateProjectCommandTests
{ {
sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).Returns(project); sutProvider.GetDependency<IProjectRepository>().GetByIdAsync(project.Id).Returns(project);
sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(project.Id, userId).Returns(true); sutProvider.GetDependency<IProjectRepository>().UserHasWriteAccessToProject(project.Id, userId).Returns(true);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(project.OrganizationId).Returns(true);
var project2 = new Project { Id = project.Id, Name = "newName" }; var project2 = new Project { Id = project.Id, Name = "newName" };
var result = await sutProvider.Sut.UpdateAsync(project2, userId); var result = await sutProvider.Sut.UpdateAsync(project2, userId);

View File

@ -1,4 +1,5 @@
using Bit.Commercial.Core.SecretsManager.Commands.Secrets; using Bit.Commercial.Core.SecretsManager.Commands.Secrets;
using Bit.Core.Context;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
@ -56,6 +57,7 @@ public class DeleteSecretCommandTests
} }
sutProvider.GetDependency<ISecretRepository>().GetManyByIds(data).Returns(secrets); sutProvider.GetDependency<ISecretRepository>().GetManyByIds(data).Returns(secrets);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(default).ReturnsForAnyArgs(true);
var results = await sutProvider.Sut.DeleteSecrets(data); var results = await sutProvider.Sut.DeleteSecrets(data);

View File

@ -1,4 +1,6 @@
using Bit.Commercial.Core.SecretsManager.Commands.ServiceAccounts; using Bit.Commercial.Core.SecretsManager.Commands.ServiceAccounts;
using Bit.Core.Entities;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
@ -15,9 +17,18 @@ public class CreateServiceAccountCommandTests
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task CreateAsync_CallsCreate(ServiceAccount data, public async Task CreateAsync_CallsCreate(ServiceAccount data,
Guid userId,
SutProvider<CreateServiceAccountCommand> sutProvider) SutProvider<CreateServiceAccountCommand> sutProvider)
{ {
await sutProvider.Sut.CreateAsync(data); sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByOrganizationAsync(Arg.Any<Guid>(), Arg.Any<Guid>())
.Returns(new OrganizationUser() { Id = userId });
sutProvider.GetDependency<IServiceAccountRepository>()
.CreateAsync(Arg.Any<ServiceAccount>())
.Returns(data);
await sutProvider.Sut.CreateAsync(data, userId);
await sutProvider.GetDependency<IServiceAccountRepository>().Received(1) await sutProvider.GetDependency<IServiceAccountRepository>().Received(1)
.CreateAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data))); .CreateAsync(Arg.Is(AssertHelper.AssertPropertyEqual(data)));

View File

@ -18,7 +18,7 @@ public class UpdateServiceAccountCommandTests
[BitAutoData] [BitAutoData]
public async Task UpdateAsync_ServiceAccountDoesNotExist_ThrowsNotFound(ServiceAccount data, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider) public async Task UpdateAsync_ServiceAccountDoesNotExist_ThrowsNotFound(ServiceAccount data, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider)
{ {
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data, userId)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data, userId));
await sutProvider.GetDependency<IServiceAccountRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default); await sutProvider.GetDependency<IServiceAccountRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
} }
@ -30,7 +30,7 @@ public class UpdateServiceAccountCommandTests
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).Returns(data); sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).Returns(data);
sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(data.Id, userId).Returns(false); sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(data.Id, userId).Returns(false);
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => sutProvider.Sut.UpdateAsync(data, userId)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(data, userId));
await sutProvider.GetDependency<IServiceAccountRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default); await sutProvider.GetDependency<IServiceAccountRepository>().DidNotReceiveWithAnyArgs().ReplaceAsync(default);
} }
@ -39,6 +39,7 @@ public class UpdateServiceAccountCommandTests
[BitAutoData] [BitAutoData]
public async Task UpdateAsync_User_Success(ServiceAccount data, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider) public async Task UpdateAsync_User_Success(ServiceAccount data, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider)
{ {
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(data.OrganizationId).Returns(true);
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).Returns(data); sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).Returns(data);
sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(data.Id, userId).Returns(true); sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(data.Id, userId).Returns(true);
@ -54,6 +55,7 @@ public class UpdateServiceAccountCommandTests
public async Task UpdateAsync_Admin_Success(ServiceAccount data, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider) public async Task UpdateAsync_Admin_Success(ServiceAccount data, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider)
{ {
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).Returns(data); sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(data.Id).Returns(data);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(data.OrganizationId).Returns(true);
sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(true); sutProvider.GetDependency<ICurrentContext>().OrganizationAdmin(data.OrganizationId).Returns(true);
await sutProvider.Sut.UpdateAsync(data, userId); await sutProvider.Sut.UpdateAsync(data, userId);
@ -66,6 +68,7 @@ public class UpdateServiceAccountCommandTests
[BitAutoData] [BitAutoData]
public async Task UpdateAsync_DoesNotModifyOrganizationId(ServiceAccount existingServiceAccount, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider) public async Task UpdateAsync_DoesNotModifyOrganizationId(ServiceAccount existingServiceAccount, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider)
{ {
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingServiceAccount.OrganizationId).Returns(true);
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(existingServiceAccount.Id).Returns(existingServiceAccount); sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(existingServiceAccount.Id).Returns(existingServiceAccount);
sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(existingServiceAccount.Id, userId).Returns(true); sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(existingServiceAccount.Id, userId).Returns(true);
@ -87,6 +90,7 @@ public class UpdateServiceAccountCommandTests
[BitAutoData] [BitAutoData]
public async Task UpdateAsync_DoesNotModifyCreationDate(ServiceAccount existingServiceAccount, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider) public async Task UpdateAsync_DoesNotModifyCreationDate(ServiceAccount existingServiceAccount, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider)
{ {
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingServiceAccount.OrganizationId).Returns(true);
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(existingServiceAccount.Id).Returns(existingServiceAccount); sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(existingServiceAccount.Id).Returns(existingServiceAccount);
sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(existingServiceAccount.Id, userId).Returns(true); sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(existingServiceAccount.Id, userId).Returns(true);
@ -108,6 +112,7 @@ public class UpdateServiceAccountCommandTests
[BitAutoData] [BitAutoData]
public async Task UpdateAsync_RevisionDateIsUpdatedToUtcNow(ServiceAccount existingServiceAccount, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider) public async Task UpdateAsync_RevisionDateIsUpdatedToUtcNow(ServiceAccount existingServiceAccount, Guid userId, SutProvider<UpdateServiceAccountCommand> sutProvider)
{ {
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(existingServiceAccount.OrganizationId).Returns(true);
sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(existingServiceAccount.Id).Returns(existingServiceAccount); sutProvider.GetDependency<IServiceAccountRepository>().GetByIdAsync(existingServiceAccount.Id).Returns(existingServiceAccount);
sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(existingServiceAccount.Id, userId).Returns(true); sutProvider.GetDependency<IServiceAccountRepository>().UserHasWriteAccessToServiceAccount(existingServiceAccount.Id, userId).Returns(true);

View File

@ -6,7 +6,15 @@
# in the future and investigate if we can migrate back. # in the future and investigate if we can migrate back.
# docker-compose --profile mssql exec mssql bash /mnt/helpers/run_migrations.sh @args # docker-compose --profile mssql exec mssql bash /mnt/helpers/run_migrations.sh @args
param([switch]$all = $false, [switch]$postgres = $false, [switch]$mysql = $false, [switch]$mssql = $false, [switch]$sqlite = $false) param(
[switch]$all = $false,
[switch]$postgres = $false,
[switch]$mysql = $false,
[switch]$mssql = $false,
[switch]$sqlite = $false,
[switch]$selfhost = $false,
[switch]$pipeline = $false
)
if (!$all -and !$postgres -and !$mysql -and !$sqlite) { if (!$all -and !$postgres -and !$mysql -and !$sqlite) {
$mssql = $true; $mssql = $true;
@ -21,6 +29,12 @@ if ($all -or $postgres -or $mysql -or $sqlite) {
} }
if ($all -or $mssql) { if ($all -or $mssql) {
if ($selfhost) {
$migrationArgs = "-s"
} elseif ($pipeline) {
$migrationArgs = "-p"
}
Write-Host "Starting Microsoft SQL Server Migrations" Write-Host "Starting Microsoft SQL Server Migrations"
docker run ` docker run `
-v "$(pwd)/helpers/mssql:/mnt/helpers" ` -v "$(pwd)/helpers/mssql:/mnt/helpers" `
@ -30,7 +44,7 @@ if ($all -or $mssql) {
--network=bitwardenserver_default ` --network=bitwardenserver_default `
--rm ` --rm `
mcr.microsoft.com/mssql-tools ` mcr.microsoft.com/mssql-tools `
/mnt/helpers/run_migrations.sh @args /mnt/helpers/run_migrations.sh $migrationArgs
} }
$currentDir = Get-Location $currentDir = Get-Location

View File

@ -227,6 +227,7 @@ RUN mkdir -p /var/log/bitwarden
RUN mkdir -p /var/log/nginx/logs RUN mkdir -p /var/log/nginx/logs
RUN mkdir -p /etc/nginx/http.d RUN mkdir -p /etc/nginx/http.d
RUN mkdir -p /var/run/nginx RUN mkdir -p /var/run/nginx
RUN mkdir -p /var/lib/nginx/tmp
RUN touch /var/run/nginx/nginx.pid RUN touch /var/run/nginx/nginx.pid
RUN mkdir -p /app RUN mkdir -p /app

View File

@ -57,7 +57,7 @@ server {
include /etc/nginx/security-headers-ssl.conf; include /etc/nginx/security-headers-ssl.conf;
{{/if}} {{/if}}
include /etc/nginx/security-headers.conf; include /etc/nginx/security-headers.conf;
add_header Content-Security-Policy "{{{String.Coalesce env.BW_CSP "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://haveibeenpwned.com; child-src 'self' https://*.duosecurity.com https://*.duofederal.com; frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; connect-src 'self' https://api.pwnedpasswords.com https://api.2fa.directory; object-src 'self' blob:;"}}}"; add_header Content-Security-Policy "{{{String.Coalesce env.BW_CSP "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://haveibeenpwned.com; child-src 'self' https://*.duosecurity.com https://*.duofederal.com; frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; connect-src 'self' https://api.pwnedpasswords.com https://api.2fa.directory; object-src 'self' blob:;"}}}";
add_header X-Frame-Options SAMEORIGIN; add_header X-Frame-Options SAMEORIGIN;
add_header X-Robots-Tag "noindex, nofollow"; add_header X-Robots-Tag "noindex, nofollow";
} }
@ -101,7 +101,7 @@ server {
root /app/Web; root /app/Web;
} }
location /attachments { location /attachments/ {
alias /etc/bitwarden/attachments/; alias /etc/bitwarden/attachments/;
} }
{{#if (String.Equal env.BW_ENABLE_API "true")}} {{#if (String.Equal env.BW_ENABLE_API "true")}}

View File

@ -3,6 +3,7 @@ using System.Text.Json;
using Bit.Admin.Models; using Bit.Admin.Models;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Models.BitStripe; using Bit.Core.Models.BitStripe;
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Settings; using Bit.Core.Settings;
@ -18,7 +19,7 @@ public class ToolsController : Controller
{ {
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationService _organizationService; private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery;
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly ITransactionRepository _transactionRepository; private readonly ITransactionRepository _transactionRepository;
private readonly IInstallationRepository _installationRepository; private readonly IInstallationRepository _installationRepository;
@ -30,7 +31,7 @@ public class ToolsController : Controller
public ToolsController( public ToolsController(
GlobalSettings globalSettings, GlobalSettings globalSettings,
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
IOrganizationService organizationService, ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery,
IUserService userService, IUserService userService,
ITransactionRepository transactionRepository, ITransactionRepository transactionRepository,
IInstallationRepository installationRepository, IInstallationRepository installationRepository,
@ -41,7 +42,7 @@ public class ToolsController : Controller
{ {
_globalSettings = globalSettings; _globalSettings = globalSettings;
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_organizationService = organizationService; _cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery;
_userService = userService; _userService = userService;
_transactionRepository = transactionRepository; _transactionRepository = transactionRepository;
_installationRepository = installationRepository; _installationRepository = installationRepository;
@ -259,7 +260,7 @@ public class ToolsController : Controller
if (organization != null) if (organization != null)
{ {
var license = await _organizationService.GenerateLicenseAsync(organization, var license = await _cloudGetOrganizationLicenseQuery.GetLicenseAsync(organization,
model.InstallationId.Value, model.Version); model.InstallationId.Value, model.Version);
var ms = new MemoryStream(); var ms = new MemoryStream();
await JsonSerializer.SerializeAsync(ms, license, JsonHelpers.Indented); await JsonSerializer.SerializeAsync(ms, license, JsonHelpers.Indented);

View File

@ -1,6 +1,9 @@
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Api.OrganizationLicenses;
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@ -14,26 +17,26 @@ namespace Bit.Api.Controllers;
[SelfHosted(NotSelfHostedOnly = true)] [SelfHosted(NotSelfHostedOnly = true)]
public class LicensesController : Controller public class LicensesController : Controller
{ {
private readonly ILicensingService _licensingService;
private readonly IUserRepository _userRepository; private readonly IUserRepository _userRepository;
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationRepository _organizationRepository;
private readonly IOrganizationService _organizationService; private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery;
private readonly IValidateBillingSyncKeyCommand _validateBillingSyncKeyCommand;
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
public LicensesController( public LicensesController(
ILicensingService licensingService,
IUserRepository userRepository, IUserRepository userRepository,
IUserService userService, IUserService userService,
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
IOrganizationService organizationService, ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery,
IValidateBillingSyncKeyCommand validateBillingSyncKeyCommand,
ICurrentContext currentContext) ICurrentContext currentContext)
{ {
_licensingService = licensingService;
_userRepository = userRepository; _userRepository = userRepository;
_userService = userService; _userService = userService;
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_organizationService = organizationService; _cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery;
_validateBillingSyncKeyCommand = validateBillingSyncKeyCommand;
_currentContext = currentContext; _currentContext = currentContext;
} }
@ -55,21 +58,30 @@ public class LicensesController : Controller
return license; return license;
} }
/// <summary>
/// Used by self-hosted installations to get an updated license file
/// </summary>
[HttpGet("organization/{id}")] [HttpGet("organization/{id}")]
public async Task<OrganizationLicense> GetOrganization(string id, [FromQuery] string key) public async Task<OrganizationLicense> OrganizationSync(string id, [FromBody] SelfHostedOrganizationLicenseRequestModel model)
{ {
var org = await _organizationRepository.GetByIdAsync(new Guid(id)); var organization = await _organizationRepository.GetByIdAsync(new Guid(id));
if (org == null) if (organization == null)
{ {
return null; throw new NotFoundException("Organization not found.");
} }
else if (!org.LicenseKey.Equals(key))
if (!organization.LicenseKey.Equals(model.LicenseKey))
{ {
await Task.Delay(2000); await Task.Delay(2000);
throw new BadRequestException("Invalid license key."); throw new BadRequestException("Invalid license key.");
} }
var license = await _organizationService.GenerateLicenseAsync(org, _currentContext.InstallationId.Value); if (!await _validateBillingSyncKeyCommand.ValidateBillingSyncKeyAsync(organization, model.BillingSyncKey))
{
throw new BadRequestException("Invalid Billing Sync Key");
}
var license = await _cloudGetOrganizationLicenseQuery.GetLicenseAsync(organization, _currentContext.InstallationId.Value);
return license; return license;
} }
} }

View File

@ -191,7 +191,7 @@ public class OrganizationConnectionsController : Controller
Guid? organizationConnectionId, Guid? organizationConnectionId,
OrganizationConnectionRequestModel model, OrganizationConnectionRequestModel model,
Func<OrganizationConnectionRequestModel<T>, Task> validateAction = null) Func<OrganizationConnectionRequestModel<T>, Task> validateAction = null)
where T : new() where T : IConnectionConfig
{ {
var typedModel = new OrganizationConnectionRequestModel<T>(model); var typedModel = new OrganizationConnectionRequestModel<T>(model);
if (validateAction != null) if (validateAction != null)

View File

@ -5,6 +5,7 @@ using Bit.Core.Entities;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Api.Request.OrganizationSponsorships; using Bit.Core.Models.Api.Request.OrganizationSponsorships;
using Bit.Core.Models.Api.Response.OrganizationSponsorships; using Bit.Core.Models.Api.Response.OrganizationSponsorships;
using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;

View File

@ -11,6 +11,7 @@ using Bit.Core.Exceptions;
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
using Bit.Core.Models.Data.Organizations.Policies; using Bit.Core.Models.Data.Organizations.Policies;
using Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Settings; using Bit.Core.Settings;
@ -37,6 +38,7 @@ public class OrganizationsController : Controller
private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand; private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand;
private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand; private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand;
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository; private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
public OrganizationsController( public OrganizationsController(
@ -53,6 +55,7 @@ public class OrganizationsController : Controller
IRotateOrganizationApiKeyCommand rotateOrganizationApiKeyCommand, IRotateOrganizationApiKeyCommand rotateOrganizationApiKeyCommand,
ICreateOrganizationApiKeyCommand createOrganizationApiKeyCommand, ICreateOrganizationApiKeyCommand createOrganizationApiKeyCommand,
IOrganizationApiKeyRepository organizationApiKeyRepository, IOrganizationApiKeyRepository organizationApiKeyRepository,
ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery,
GlobalSettings globalSettings) GlobalSettings globalSettings)
{ {
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
@ -68,6 +71,7 @@ public class OrganizationsController : Controller
_rotateOrganizationApiKeyCommand = rotateOrganizationApiKeyCommand; _rotateOrganizationApiKeyCommand = rotateOrganizationApiKeyCommand;
_createOrganizationApiKeyCommand = createOrganizationApiKeyCommand; _createOrganizationApiKeyCommand = createOrganizationApiKeyCommand;
_organizationApiKeyRepository = organizationApiKeyRepository; _organizationApiKeyRepository = organizationApiKeyRepository;
_cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery;
_globalSettings = globalSettings; _globalSettings = globalSettings;
} }
@ -149,7 +153,8 @@ public class OrganizationsController : Controller
throw new NotFoundException(); throw new NotFoundException();
} }
var license = await _organizationService.GenerateLicenseAsync(orgIdGuid, installationId); var org = await _organizationRepository.GetByIdAsync(new Guid(id));
var license = await _cloudGetOrganizationLicenseQuery.GetLicenseAsync(org, installationId);
if (license == null) if (license == null)
{ {
throw new NotFoundException(); throw new NotFoundException();
@ -215,6 +220,7 @@ public class OrganizationsController : Controller
return new OrganizationResponseModel(result.Item1); return new OrganizationResponseModel(result.Item1);
} }
[Obsolete("2022-12-7 Moved to SelfHostedOrganizationLicensesController, to be removed in EC-815")]
[HttpPost("license")] [HttpPost("license")]
[SelfHosted(SelfHostedOnly = true)] [SelfHosted(SelfHostedOnly = true)]
public async Task<OrganizationResponseModel> PostLicense(OrganizationCreateLicenseRequestModel model) public async Task<OrganizationResponseModel> PostLicense(OrganizationCreateLicenseRequestModel model)
@ -448,6 +454,7 @@ public class OrganizationsController : Controller
} }
} }
[Obsolete("2022-12-7 Moved to SelfHostedOrganizationLicensesController, to be removed in EC-815")]
[HttpPost("{id}/license")] [HttpPost("{id}/license")]
[SelfHosted(SelfHostedOnly = true)] [SelfHosted(SelfHostedOnly = true)]
public async Task PostLicense(string id, LicenseRequestModel model) public async Task PostLicense(string id, LicenseRequestModel model)

View File

@ -0,0 +1,117 @@
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;
using Bit.Core.Exceptions;
using Bit.Core.Models.Business;
using Bit.Core.Models.OrganizationConnectionConfigs;
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers.SelfHosted;
[Route("organizations/licenses/self-hosted")]
[Authorize("Application")]
[SelfHosted(SelfHostedOnly = true)]
public class SelfHostedOrganizationLicensesController : Controller
{
private readonly ICurrentContext _currentContext;
private readonly ISelfHostedGetOrganizationLicenseQuery _selfHostedGetOrganizationLicenseQuery;
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
private readonly IOrganizationService _organizationService;
private readonly IOrganizationRepository _organizationRepository;
private readonly IUserService _userService;
public SelfHostedOrganizationLicensesController(
ICurrentContext currentContext,
ISelfHostedGetOrganizationLicenseQuery selfHostedGetOrganizationLicenseQuery,
IOrganizationConnectionRepository organizationConnectionRepository,
IOrganizationService organizationService,
IOrganizationRepository organizationRepository,
IUserService userService)
{
_currentContext = currentContext;
_selfHostedGetOrganizationLicenseQuery = selfHostedGetOrganizationLicenseQuery;
_organizationConnectionRepository = organizationConnectionRepository;
_organizationService = organizationService;
_organizationRepository = organizationRepository;
_userService = userService;
}
[HttpPost("")]
public async Task<OrganizationResponseModel> PostLicenseAsync(OrganizationCreateLicenseRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}
var license = await ApiHelpers.ReadJsonFileFromBody<OrganizationLicense>(HttpContext, model.License);
if (license == null)
{
throw new BadRequestException("Invalid license");
}
var result = await _organizationService.SignUpAsync(license, user, model.Key,
model.CollectionName, model.Keys?.PublicKey, model.Keys?.EncryptedPrivateKey);
return new OrganizationResponseModel(result.Item1);
}
[HttpPost("{id}")]
public async Task PostLicenseAsync(string id, LicenseRequestModel model)
{
var orgIdGuid = new Guid(id);
if (!await _currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
var license = await ApiHelpers.ReadJsonFileFromBody<OrganizationLicense>(HttpContext, model.License);
if (license == null)
{
throw new BadRequestException("Invalid license");
}
await _organizationService.UpdateLicenseAsync(new Guid(id), license);
}
[HttpPost("{id}/sync")]
public async Task SyncLicenseAsync(string id)
{
var organization = await _organizationRepository.GetByIdAsync(new Guid(id));
if (organization == null)
{
throw new NotFoundException();
}
if (!await _currentContext.OrganizationOwner(organization.Id))
{
throw new NotFoundException();
}
var billingSyncConnection =
(await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organization.Id,
OrganizationConnectionType.CloudBillingSync)).FirstOrDefault();
if (billingSyncConnection == null)
{
throw new NotFoundException("Unable to get Cloud Billing Sync connection");
}
var license =
await _selfHostedGetOrganizationLicenseQuery.GetLicenseAsync(organization, billingSyncConnection);
await _organizationService.UpdateLicenseAsync(organization.Id, license);
var config = billingSyncConnection.GetConfig<BillingSyncConfig>();
config.LastLicenseSync = DateTime.Now;
billingSyncConnection.SetConfig(config);
await _organizationConnectionRepository.ReplaceAsync(billingSyncConnection);
}
}

View File

@ -2,6 +2,7 @@
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Data.Organizations.OrganizationConnections; using Bit.Core.Models.Data.Organizations.OrganizationConnections;
using Bit.Core.Models.OrganizationConnectionConfigs;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Api.Models.Request.Organizations; namespace Bit.Api.Models.Request.Organizations;
@ -17,7 +18,7 @@ public class OrganizationConnectionRequestModel
} }
public class OrganizationConnectionRequestModel<T> : OrganizationConnectionRequestModel where T : new() public class OrganizationConnectionRequestModel<T> : OrganizationConnectionRequestModel where T : IConnectionConfig
{ {
public T ParsedConfig { get; private set; } public T ParsedConfig { get; private set; }

View File

@ -17,6 +17,7 @@ public class OrganizationUserInviteRequestModel
[Required] [Required]
public OrganizationUserType? Type { get; set; } public OrganizationUserType? Type { get; set; }
public bool AccessAll { get; set; } public bool AccessAll { get; set; }
public bool AccessSecretsManager { get; set; }
public Permissions Permissions { get; set; } public Permissions Permissions { get; set; }
public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; } public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; }
public IEnumerable<Guid> Groups { get; set; } public IEnumerable<Guid> Groups { get; set; }
@ -28,6 +29,7 @@ public class OrganizationUserInviteRequestModel
Emails = Emails, Emails = Emails,
Type = Type, Type = Type,
AccessAll = AccessAll, AccessAll = AccessAll,
AccessSecretsManager = AccessSecretsManager,
Collections = Collections?.Select(c => c.ToSelectionReadOnly()), Collections = Collections?.Select(c => c.ToSelectionReadOnly()),
Groups = Groups, Groups = Groups,
Permissions = Permissions, Permissions = Permissions,
@ -73,6 +75,7 @@ public class OrganizationUserUpdateRequestModel
[Required] [Required]
public OrganizationUserType? Type { get; set; } public OrganizationUserType? Type { get; set; }
public bool AccessAll { get; set; } public bool AccessAll { get; set; }
public bool AccessSecretsManager { get; set; }
public Permissions Permissions { get; set; } public Permissions Permissions { get; set; }
public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; } public IEnumerable<SelectionReadOnlyRequestModel> Collections { get; set; }
public IEnumerable<Guid> Groups { get; set; } public IEnumerable<Guid> Groups { get; set; }
@ -85,6 +88,7 @@ public class OrganizationUserUpdateRequestModel
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
}); });
existingUser.AccessAll = AccessAll; existingUser.AccessAll = AccessAll;
existingUser.AccessSecretsManager = AccessSecretsManager;
return existingUser; return existingUser;
} }
} }

View File

@ -23,6 +23,7 @@ public class OrganizationUserResponseModel : ResponseModel
Type = organizationUser.Type; Type = organizationUser.Type;
Status = organizationUser.Status; Status = organizationUser.Status;
AccessAll = organizationUser.AccessAll; AccessAll = organizationUser.AccessAll;
AccessSecretsManager = organizationUser.AccessSecretsManager;
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(organizationUser.Permissions); Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(organizationUser.Permissions);
ResetPasswordEnrolled = !string.IsNullOrEmpty(organizationUser.ResetPasswordKey); ResetPasswordEnrolled = !string.IsNullOrEmpty(organizationUser.ResetPasswordKey);
} }
@ -40,6 +41,7 @@ public class OrganizationUserResponseModel : ResponseModel
Type = organizationUser.Type; Type = organizationUser.Type;
Status = organizationUser.Status; Status = organizationUser.Status;
AccessAll = organizationUser.AccessAll; AccessAll = organizationUser.AccessAll;
AccessSecretsManager = organizationUser.AccessSecretsManager;
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(organizationUser.Permissions); Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(organizationUser.Permissions);
ResetPasswordEnrolled = !string.IsNullOrEmpty(organizationUser.ResetPasswordKey); ResetPasswordEnrolled = !string.IsNullOrEmpty(organizationUser.ResetPasswordKey);
UsesKeyConnector = organizationUser.UsesKeyConnector; UsesKeyConnector = organizationUser.UsesKeyConnector;
@ -50,6 +52,7 @@ public class OrganizationUserResponseModel : ResponseModel
public OrganizationUserType Type { get; set; } public OrganizationUserType Type { get; set; }
public OrganizationUserStatusType Status { get; set; } public OrganizationUserStatusType Status { get; set; }
public bool AccessAll { get; set; } public bool AccessAll { get; set; }
public bool AccessSecretsManager { get; set; }
public Permissions Permissions { get; set; } public Permissions Permissions { get; set; }
public bool ResetPasswordEnrolled { get; set; } public bool ResetPasswordEnrolled { get; set; }
public bool UsesKeyConnector { get; set; } public bool UsesKeyConnector { get; set; }

View File

@ -52,6 +52,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
FamilySponsorshipLastSyncDate = organization.FamilySponsorshipLastSyncDate; FamilySponsorshipLastSyncDate = organization.FamilySponsorshipLastSyncDate;
FamilySponsorshipToDelete = organization.FamilySponsorshipToDelete; FamilySponsorshipToDelete = organization.FamilySponsorshipToDelete;
FamilySponsorshipValidUntil = organization.FamilySponsorshipValidUntil; FamilySponsorshipValidUntil = organization.FamilySponsorshipValidUntil;
AccessSecretsManager = organization.AccessSecretsManager;
if (organization.SsoConfig != null) if (organization.SsoConfig != null)
{ {
@ -101,4 +102,5 @@ public class ProfileOrganizationResponseModel : ResponseModel
public DateTime? FamilySponsorshipLastSyncDate { get; set; } public DateTime? FamilySponsorshipLastSyncDate { get; set; }
public DateTime? FamilySponsorshipValidUntil { get; set; } public DateTime? FamilySponsorshipValidUntil { get; set; }
public bool? FamilySponsorshipToDelete { get; set; } public bool? FamilySponsorshipToDelete { get; set; }
public bool AccessSecretsManager { get; set; }
} }

View File

@ -1,5 +1,6 @@
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Utilities;
namespace Bit.Api.Models.Response; namespace Bit.Api.Models.Response;
@ -39,5 +40,6 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo
UserId = organization.UserId?.ToString(); UserId = organization.UserId?.ToString();
ProviderId = organization.ProviderId?.ToString(); ProviderId = organization.ProviderId?.ToString();
ProviderName = organization.ProviderName; ProviderName = organization.ProviderName;
PlanProductType = StaticStore.GetPlan(organization.PlanType).Product;
} }
} }

View File

@ -40,7 +40,7 @@ public class AccessPoliciesController : Controller
[HttpGet("/projects/{id}/access-policies")] [HttpGet("/projects/{id}/access-policies")]
public async Task<ProjectAccessPoliciesResponseModel> GetProjectAccessPoliciesAsync([FromRoute] Guid id) public async Task<ProjectAccessPoliciesResponseModel> GetProjectAccessPoliciesAsync([FromRoute] Guid id)
{ {
var results = await _accessPolicyRepository.GetManyByProjectId(id); var results = await _accessPolicyRepository.GetManyByGrantedProjectIdAsync(id);
return new ProjectAccessPoliciesResponseModel(results); return new ProjectAccessPoliciesResponseModel(results);
} }

View File

@ -7,61 +7,46 @@ using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.Projects.Interfaces; using Bit.Core.SecretsManager.Commands.Projects.Interfaces;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.SecretsManager.Controllers; namespace Bit.Api.SecretsManager.Controllers;
[SecretsManager] [SecretsManager]
[Authorize("secrets")]
public class ProjectsController : Controller public class ProjectsController : Controller
{ {
private readonly ICurrentContext _currentContext;
private readonly IUserService _userService; private readonly IUserService _userService;
private readonly IProjectRepository _projectRepository; private readonly IProjectRepository _projectRepository;
private readonly ICreateProjectCommand _createProjectCommand; private readonly ICreateProjectCommand _createProjectCommand;
private readonly IUpdateProjectCommand _updateProjectCommand; private readonly IUpdateProjectCommand _updateProjectCommand;
private readonly IDeleteProjectCommand _deleteProjectCommand; private readonly IDeleteProjectCommand _deleteProjectCommand;
private readonly ICurrentContext _currentContext;
public ProjectsController( public ProjectsController(
ICurrentContext currentContext,
IUserService userService, IUserService userService,
IProjectRepository projectRepository, IProjectRepository projectRepository,
ICreateProjectCommand createProjectCommand, ICreateProjectCommand createProjectCommand,
IUpdateProjectCommand updateProjectCommand, IUpdateProjectCommand updateProjectCommand,
IDeleteProjectCommand deleteProjectCommand, IDeleteProjectCommand deleteProjectCommand)
ICurrentContext currentContext)
{ {
_currentContext = currentContext;
_userService = userService; _userService = userService;
_projectRepository = projectRepository; _projectRepository = projectRepository;
_createProjectCommand = createProjectCommand; _createProjectCommand = createProjectCommand;
_updateProjectCommand = updateProjectCommand; _updateProjectCommand = updateProjectCommand;
_deleteProjectCommand = deleteProjectCommand; _deleteProjectCommand = deleteProjectCommand;
_currentContext = currentContext;
} }
[HttpPost("organizations/{organizationId}/projects")] [HttpGet("organizations/{organizationId}/projects")]
public async Task<ProjectResponseModel> CreateAsync([FromRoute] Guid organizationId, [FromBody] ProjectCreateRequestModel createRequest) public async Task<ListResponseModel<ProjectResponseModel>> ListByOrganizationAsync([FromRoute] Guid organizationId)
{ {
if (!await _currentContext.OrganizationUser(organizationId)) if (!_currentContext.AccessSecretsManager(organizationId))
{ {
throw new NotFoundException(); throw new NotFoundException();
} }
var result = await _createProjectCommand.CreateAsync(createRequest.ToProject(organizationId));
return new ProjectResponseModel(result);
}
[HttpPut("projects/{id}")]
public async Task<ProjectResponseModel> UpdateProjectAsync([FromRoute] Guid id, [FromBody] ProjectUpdateRequestModel updateRequest)
{
var userId = _userService.GetProperUserId(User).Value;
var result = await _updateProjectCommand.UpdateAsync(updateRequest.ToProject(id), userId);
return new ProjectResponseModel(result);
}
[HttpGet("organizations/{organizationId}/projects")]
public async Task<ListResponseModel<ProjectResponseModel>> GetProjectsByOrganizationAsync(
[FromRoute] Guid organizationId)
{
var userId = _userService.GetProperUserId(User).Value; var userId = _userService.GetProperUserId(User).Value;
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId); var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
@ -72,8 +57,29 @@ public class ProjectsController : Controller
return new ListResponseModel<ProjectResponseModel>(responses); return new ListResponseModel<ProjectResponseModel>(responses);
} }
[HttpPost("organizations/{organizationId}/projects")]
public async Task<ProjectResponseModel> CreateAsync([FromRoute] Guid organizationId, [FromBody] ProjectCreateRequestModel createRequest)
{
if (!_currentContext.AccessSecretsManager(organizationId))
{
throw new NotFoundException();
}
var result = await _createProjectCommand.CreateAsync(createRequest.ToProject(organizationId));
return new ProjectResponseModel(result);
}
[HttpPut("projects/{id}")]
public async Task<ProjectResponseModel> UpdateAsync([FromRoute] Guid id, [FromBody] ProjectUpdateRequestModel updateRequest)
{
var userId = _userService.GetProperUserId(User).Value;
var result = await _updateProjectCommand.UpdateAsync(updateRequest.ToProject(id), userId);
return new ProjectResponseModel(result);
}
[HttpGet("projects/{id}")] [HttpGet("projects/{id}")]
public async Task<ProjectResponseModel> GetProjectAsync([FromRoute] Guid id) public async Task<ProjectResponseModel> GetAsync([FromRoute] Guid id)
{ {
var project = await _projectRepository.GetByIdAsync(id); var project = await _projectRepository.GetByIdAsync(id);
if (project == null) if (project == null)
@ -81,6 +87,11 @@ public class ProjectsController : Controller
throw new NotFoundException(); throw new NotFoundException();
} }
if (!_currentContext.AccessSecretsManager(project.OrganizationId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User).Value; var userId = _userService.GetProperUserId(User).Value;
var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId); var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
@ -101,7 +112,7 @@ public class ProjectsController : Controller
} }
[HttpPost("projects/delete")] [HttpPost("projects/delete")]
public async Task<ListResponseModel<BulkDeleteResponseModel>> BulkDeleteProjectsAsync([FromBody] List<Guid> ids) public async Task<ListResponseModel<BulkDeleteResponseModel>> BulkDeleteAsync([FromBody] List<Guid> ids)
{ {
var userId = _userService.GetProperUserId(User).Value; var userId = _userService.GetProperUserId(User).Value;

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.Context;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces; using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
@ -13,30 +14,52 @@ namespace Bit.Api.SecretsManager.Controllers;
[Authorize("secrets")] [Authorize("secrets")]
public class SecretsController : Controller public class SecretsController : Controller
{ {
private readonly ICurrentContext _currentContext;
private readonly ISecretRepository _secretRepository; private readonly ISecretRepository _secretRepository;
private readonly IProjectRepository _projectRepository;
private readonly ICreateSecretCommand _createSecretCommand; private readonly ICreateSecretCommand _createSecretCommand;
private readonly IUpdateSecretCommand _updateSecretCommand; private readonly IUpdateSecretCommand _updateSecretCommand;
private readonly IDeleteSecretCommand _deleteSecretCommand; private readonly IDeleteSecretCommand _deleteSecretCommand;
public SecretsController(ISecretRepository secretRepository, IProjectRepository projectRepository, ICreateSecretCommand createSecretCommand, IUpdateSecretCommand updateSecretCommand, IDeleteSecretCommand deleteSecretCommand) public SecretsController(
ICurrentContext currentContext,
ISecretRepository secretRepository,
ICreateSecretCommand createSecretCommand,
IUpdateSecretCommand updateSecretCommand,
IDeleteSecretCommand deleteSecretCommand)
{ {
_currentContext = currentContext;
_secretRepository = secretRepository; _secretRepository = secretRepository;
_projectRepository = projectRepository;
_createSecretCommand = createSecretCommand; _createSecretCommand = createSecretCommand;
_updateSecretCommand = updateSecretCommand; _updateSecretCommand = updateSecretCommand;
_deleteSecretCommand = deleteSecretCommand; _deleteSecretCommand = deleteSecretCommand;
} }
[HttpGet("organizations/{organizationId}/secrets")] [HttpGet("organizations/{organizationId}/secrets")]
public async Task<SecretWithProjectsListResponseModel> GetSecretsByOrganizationAsync([FromRoute] Guid organizationId) public async Task<SecretWithProjectsListResponseModel> ListByOrganizationAsync([FromRoute] Guid organizationId)
{ {
if (!_currentContext.AccessSecretsManager(organizationId))
{
throw new NotFoundException();
}
var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId); var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId);
return new SecretWithProjectsListResponseModel(secrets); return new SecretWithProjectsListResponseModel(secrets);
} }
[HttpPost("organizations/{organizationId}/secrets")]
public async Task<SecretResponseModel> CreateAsync([FromRoute] Guid organizationId, [FromBody] SecretCreateRequestModel createRequest)
{
if (!_currentContext.AccessSecretsManager(organizationId))
{
throw new NotFoundException();
}
var result = await _createSecretCommand.CreateAsync(createRequest.ToSecret(organizationId));
return new SecretResponseModel(result);
}
[HttpGet("secrets/{id}")] [HttpGet("secrets/{id}")]
public async Task<SecretResponseModel> GetSecretAsync([FromRoute] Guid id) public async Task<SecretResponseModel> GetAsync([FromRoute] Guid id)
{ {
var secret = await _secretRepository.GetByIdAsync(id); var secret = await _secretRepository.GetByIdAsync(id);
if (secret == null) if (secret == null)
@ -54,15 +77,8 @@ public class SecretsController : Controller
return new SecretWithProjectsListResponseModel(secrets); return new SecretWithProjectsListResponseModel(secrets);
} }
[HttpPost("organizations/{organizationId}/secrets")]
public async Task<SecretResponseModel> CreateSecretAsync([FromRoute] Guid organizationId, [FromBody] SecretCreateRequestModel createRequest)
{
var result = await _createSecretCommand.CreateAsync(createRequest.ToSecret(organizationId));
return new SecretResponseModel(result);
}
[HttpPut("secrets/{id}")] [HttpPut("secrets/{id}")]
public async Task<SecretResponseModel> UpdateSecretAsync([FromRoute] Guid id, [FromBody] SecretUpdateRequestModel updateRequest) public async Task<SecretResponseModel> UpdateAsync([FromRoute] Guid id, [FromBody] SecretUpdateRequestModel updateRequest)
{ {
var result = await _updateSecretCommand.UpdateAsync(updateRequest.ToSecret(id)); var result = await _updateSecretCommand.UpdateAsync(updateRequest.ToSecret(id));
return new SecretResponseModel(result); return new SecretResponseModel(result);

View File

@ -8,43 +8,50 @@ using Bit.Core.SecretsManager.Commands.AccessTokens.Interfaces;
using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces; using Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces;
using Bit.Core.SecretsManager.Repositories; using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.SecretsManager.Controllers; namespace Bit.Api.SecretsManager.Controllers;
[SecretsManager] [SecretsManager]
[Authorize("secrets")]
[Route("service-accounts")] [Route("service-accounts")]
public class ServiceAccountsController : Controller public class ServiceAccountsController : Controller
{ {
private readonly ICurrentContext _currentContext;
private readonly IApiKeyRepository _apiKeyRepository; private readonly IApiKeyRepository _apiKeyRepository;
private readonly ICreateAccessTokenCommand _createAccessTokenCommand; private readonly ICreateAccessTokenCommand _createAccessTokenCommand;
private readonly ICreateServiceAccountCommand _createServiceAccountCommand; private readonly ICreateServiceAccountCommand _createServiceAccountCommand;
private readonly ICurrentContext _currentContext;
private readonly IServiceAccountRepository _serviceAccountRepository; private readonly IServiceAccountRepository _serviceAccountRepository;
private readonly IUpdateServiceAccountCommand _updateServiceAccountCommand; private readonly IUpdateServiceAccountCommand _updateServiceAccountCommand;
private readonly IUserService _userService; private readonly IUserService _userService;
public ServiceAccountsController( public ServiceAccountsController(
ICurrentContext currentContext,
IUserService userService, IUserService userService,
IServiceAccountRepository serviceAccountRepository, IServiceAccountRepository serviceAccountRepository,
ICreateAccessTokenCommand createAccessTokenCommand, ICreateAccessTokenCommand createAccessTokenCommand,
IApiKeyRepository apiKeyRepository, ICreateServiceAccountCommand createServiceAccountCommand, IApiKeyRepository apiKeyRepository, ICreateServiceAccountCommand createServiceAccountCommand,
IUpdateServiceAccountCommand updateServiceAccountCommand, IUpdateServiceAccountCommand updateServiceAccountCommand)
ICurrentContext currentContext)
{ {
_currentContext = currentContext;
_userService = userService; _userService = userService;
_serviceAccountRepository = serviceAccountRepository; _serviceAccountRepository = serviceAccountRepository;
_apiKeyRepository = apiKeyRepository; _apiKeyRepository = apiKeyRepository;
_createServiceAccountCommand = createServiceAccountCommand; _createServiceAccountCommand = createServiceAccountCommand;
_updateServiceAccountCommand = updateServiceAccountCommand; _updateServiceAccountCommand = updateServiceAccountCommand;
_createAccessTokenCommand = createAccessTokenCommand; _createAccessTokenCommand = createAccessTokenCommand;
_currentContext = currentContext;
} }
[HttpGet("/organizations/{organizationId}/service-accounts")] [HttpGet("/organizations/{organizationId}/service-accounts")]
public async Task<ListResponseModel<ServiceAccountResponseModel>> GetServiceAccountsByOrganizationAsync( public async Task<ListResponseModel<ServiceAccountResponseModel>> ListByOrganizationAsync(
[FromRoute] Guid organizationId) [FromRoute] Guid organizationId)
{ {
if (!_currentContext.AccessSecretsManager(organizationId))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User).Value; var userId = _userService.GetProperUserId(User).Value;
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId); var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
@ -57,20 +64,20 @@ public class ServiceAccountsController : Controller
} }
[HttpPost("/organizations/{organizationId}/service-accounts")] [HttpPost("/organizations/{organizationId}/service-accounts")]
public async Task<ServiceAccountResponseModel> CreateServiceAccountAsync([FromRoute] Guid organizationId, public async Task<ServiceAccountResponseModel> CreateAsync([FromRoute] Guid organizationId,
[FromBody] ServiceAccountCreateRequestModel createRequest) [FromBody] ServiceAccountCreateRequestModel createRequest)
{ {
if (!await _currentContext.OrganizationUser(organizationId)) if (!_currentContext.AccessSecretsManager(organizationId))
{ {
throw new NotFoundException(); throw new NotFoundException();
} }
var userId = _userService.GetProperUserId(User).Value;
var result = await _createServiceAccountCommand.CreateAsync(createRequest.ToServiceAccount(organizationId)); var result = await _createServiceAccountCommand.CreateAsync(createRequest.ToServiceAccount(organizationId), userId);
return new ServiceAccountResponseModel(result); return new ServiceAccountResponseModel(result);
} }
[HttpPut("{id}")] [HttpPut("{id}")]
public async Task<ServiceAccountResponseModel> UpdateServiceAccountAsync([FromRoute] Guid id, public async Task<ServiceAccountResponseModel> UpdateAsync([FromRoute] Guid id,
[FromBody] ServiceAccountUpdateRequestModel updateRequest) [FromBody] ServiceAccountUpdateRequestModel updateRequest)
{ {
var userId = _userService.GetProperUserId(User).Value; var userId = _userService.GetProperUserId(User).Value;
@ -89,6 +96,11 @@ public class ServiceAccountsController : Controller
throw new NotFoundException(); throw new NotFoundException();
} }
if (!_currentContext.AccessSecretsManager(serviceAccount.OrganizationId))
{
throw new NotFoundException();
}
var orgAdmin = await _currentContext.OrganizationAdmin(serviceAccount.OrganizationId); var orgAdmin = await _currentContext.OrganizationAdmin(serviceAccount.OrganizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin); var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);

View File

@ -10,11 +10,6 @@ public class ProjectAccessPoliciesResponseModel : ResponseModel
public ProjectAccessPoliciesResponseModel(IEnumerable<BaseAccessPolicy> baseAccessPolicies) public ProjectAccessPoliciesResponseModel(IEnumerable<BaseAccessPolicy> baseAccessPolicies)
: base(_objectName) : base(_objectName)
{ {
if (baseAccessPolicies == null)
{
return;
}
foreach (var baseAccessPolicy in baseAccessPolicies) foreach (var baseAccessPolicy in baseAccessPolicies)
switch (baseAccessPolicy) switch (baseAccessPolicy)
{ {

View File

@ -33,6 +33,9 @@ public class Startup
StripeConfiguration.ApiKey = globalSettings.Stripe.ApiKey; StripeConfiguration.ApiKey = globalSettings.Stripe.ApiKey;
StripeConfiguration.MaxNetworkRetries = globalSettings.Stripe.MaxNetworkRetries; StripeConfiguration.MaxNetworkRetries = globalSettings.Stripe.MaxNetworkRetries;
// Data Protection
services.AddCustomDataProtectionServices(Environment, globalSettings);
// Repositories // Repositories
services.AddDatabaseRepositories(globalSettings); services.AddDatabaseRepositories(globalSettings);

View File

@ -1,6 +1,6 @@
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.Utilities; using Bit.Core.Utilities;
namespace Bit.Core.Context; namespace Bit.Core.Context;
@ -9,14 +9,16 @@ public class CurrentContentOrganization
{ {
public CurrentContentOrganization() { } public CurrentContentOrganization() { }
public CurrentContentOrganization(OrganizationUser orgUser) public CurrentContentOrganization(OrganizationUserOrganizationDetails orgUser)
{ {
Id = orgUser.OrganizationId; Id = orgUser.OrganizationId;
Type = orgUser.Type; Type = orgUser.Type;
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(orgUser.Permissions); Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(orgUser.Permissions);
AccessSecretsManager = orgUser.AccessSecretsManager && orgUser.UseSecretsManager;
} }
public Guid Id { get; set; } public Guid Id { get; set; }
public OrganizationUserType Type { get; set; } public OrganizationUserType Type { get; set; }
public Permissions Permissions { get; set; } public Permissions Permissions { get; set; }
public bool AccessSecretsManager { get; set; }
} }

View File

@ -157,6 +157,10 @@ public class CurrentContext : ICurrentContext
private List<CurrentContentOrganization> GetOrganizations(Dictionary<string, IEnumerable<Claim>> claimsDict, bool orgApi) private List<CurrentContentOrganization> GetOrganizations(Dictionary<string, IEnumerable<Claim>> claimsDict, bool orgApi)
{ {
var accessSecretsManager = claimsDict.ContainsKey(Claims.SecretsManagerAccess)
? claimsDict[Claims.SecretsManagerAccess].ToDictionary(s => s.Value, _ => true)
: new Dictionary<string, bool>();
var organizations = new List<CurrentContentOrganization>(); var organizations = new List<CurrentContentOrganization>();
if (claimsDict.ContainsKey(Claims.OrganizationOwner)) if (claimsDict.ContainsKey(Claims.OrganizationOwner))
{ {
@ -164,7 +168,8 @@ public class CurrentContext : ICurrentContext
new CurrentContentOrganization new CurrentContentOrganization
{ {
Id = new Guid(c.Value), Id = new Guid(c.Value),
Type = OrganizationUserType.Owner Type = OrganizationUserType.Owner,
AccessSecretsManager = accessSecretsManager.ContainsKey(c.Value),
})); }));
} }
else if (orgApi && OrganizationId.HasValue) else if (orgApi && OrganizationId.HasValue)
@ -172,7 +177,7 @@ public class CurrentContext : ICurrentContext
organizations.Add(new CurrentContentOrganization organizations.Add(new CurrentContentOrganization
{ {
Id = OrganizationId.Value, Id = OrganizationId.Value,
Type = OrganizationUserType.Owner Type = OrganizationUserType.Owner,
}); });
} }
@ -182,7 +187,8 @@ public class CurrentContext : ICurrentContext
new CurrentContentOrganization new CurrentContentOrganization
{ {
Id = new Guid(c.Value), Id = new Guid(c.Value),
Type = OrganizationUserType.Admin Type = OrganizationUserType.Admin,
AccessSecretsManager = accessSecretsManager.ContainsKey(c.Value),
})); }));
} }
@ -192,7 +198,8 @@ public class CurrentContext : ICurrentContext
new CurrentContentOrganization new CurrentContentOrganization
{ {
Id = new Guid(c.Value), Id = new Guid(c.Value),
Type = OrganizationUserType.User Type = OrganizationUserType.User,
AccessSecretsManager = accessSecretsManager.ContainsKey(c.Value),
})); }));
} }
@ -202,7 +209,8 @@ public class CurrentContext : ICurrentContext
new CurrentContentOrganization new CurrentContentOrganization
{ {
Id = new Guid(c.Value), Id = new Guid(c.Value),
Type = OrganizationUserType.Manager Type = OrganizationUserType.Manager,
AccessSecretsManager = accessSecretsManager.ContainsKey(c.Value),
})); }));
} }
@ -213,7 +221,8 @@ public class CurrentContext : ICurrentContext
{ {
Id = new Guid(c.Value), Id = new Guid(c.Value),
Type = OrganizationUserType.Custom, Type = OrganizationUserType.Custom,
Permissions = SetOrganizationPermissionsFromClaims(c.Value, claimsDict) Permissions = SetOrganizationPermissionsFromClaims(c.Value, claimsDict),
AccessSecretsManager = accessSecretsManager.ContainsKey(c.Value),
})); }));
} }
@ -434,12 +443,17 @@ public class CurrentContext : ICurrentContext
return po?.ProviderId; return po?.ProviderId;
} }
public bool AccessSecretsManager(Guid orgId)
{
return Organizations?.Any(o => o.Id == orgId && o.AccessSecretsManager) ?? false;
}
public async Task<ICollection<CurrentContentOrganization>> OrganizationMembershipAsync( public async Task<ICollection<CurrentContentOrganization>> OrganizationMembershipAsync(
IOrganizationUserRepository organizationUserRepository, Guid userId) IOrganizationUserRepository organizationUserRepository, Guid userId)
{ {
if (Organizations == null) if (Organizations == null)
{ {
var userOrgs = await organizationUserRepository.GetManyByUserAsync(userId); var userOrgs = await organizationUserRepository.GetManyDetailsByUserAsync(userId);
Organizations = userOrgs.Where(ou => ou.Status == OrganizationUserStatusType.Confirmed) Organizations = userOrgs.Where(ou => ou.Status == OrganizationUserStatusType.Confirmed)
.Select(ou => new CurrentContentOrganization(ou)).ToList(); .Select(ou => new CurrentContentOrganization(ou)).ToList();
} }

View File

@ -68,4 +68,5 @@ public interface ICurrentContext
IProviderUserRepository providerUserRepository, Guid userId); IProviderUserRepository providerUserRepository, Guid userId);
Task<Guid?> ProviderIdForOrg(Guid orgId); Task<Guid?> ProviderIdForOrg(Guid orgId);
bool AccessSecretsManager(Guid organizationId);
} }

View File

@ -1,15 +1,16 @@
using System.Text.Json; using System.Text.Json;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.OrganizationConnectionConfigs;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Core.Entities; namespace Bit.Core.Entities;
public class OrganizationConnection<T> : OrganizationConnection where T : new() public class OrganizationConnection<T> : OrganizationConnection where T : IConnectionConfig
{ {
public new T Config public new T Config
{ {
get => base.GetConfig<T>(); get => base.GetConfig<T>();
set => base.SetConfig<T>(value); set => base.SetConfig(value);
} }
} }
@ -26,7 +27,7 @@ public class OrganizationConnection : ITableObject<Guid>
Id = CoreHelpers.GenerateComb(); Id = CoreHelpers.GenerateComb();
} }
public T GetConfig<T>() where T : new() public T GetConfig<T>() where T : IConnectionConfig
{ {
try try
{ {
@ -38,8 +39,32 @@ public class OrganizationConnection : ITableObject<Guid>
} }
} }
public void SetConfig<T>(T config) where T : new() public void SetConfig<T>(T config) where T : IConnectionConfig
{ {
Config = JsonSerializer.Serialize(config); Config = JsonSerializer.Serialize(config);
} }
public bool Validate<T>(out string exception) where T : IConnectionConfig
{
if (!Enabled)
{
exception = $"Connection disabled for organization {OrganizationId}";
return false;
}
if (string.IsNullOrWhiteSpace(Config))
{
exception = $"No saved Connection config for organization {OrganizationId}";
return false;
}
var config = GetConfig<T>();
if (config == null)
{
exception = $"Error parsing Connection config for organization {OrganizationId}";
return false;
}
return config.Validate(out exception);
}
} }

View File

@ -22,6 +22,7 @@ public class OrganizationUser : ITableObject<Guid>, IExternal
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow; public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
public string Permissions { get; set; } public string Permissions { get; set; }
public bool AccessSecretsManager { get; set; }
public void SetNewId() public void SetNewId()
{ {

View File

@ -65,6 +65,10 @@ public class User : ITableObject<Guid>, ISubscriber, IStorable, IStorableSubscri
public bool UnknownDeviceVerificationEnabled { get; set; } public bool UnknownDeviceVerificationEnabled { get; set; }
[MaxLength(7)] [MaxLength(7)]
public string AvatarColor { get; set; } public string AvatarColor { get; set; }
public DateTime? LastPasswordChangeDate { get; set; }
public DateTime? LastKdfChangeDate { get; set; }
public DateTime? LastKeyRotationDate { get; set; }
public DateTime? LastEmailChangeDate { get; set; }
public void SetNewId() public void SetNewId()
{ {

View File

@ -6,6 +6,7 @@ public static class Claims
public const string SecurityStamp = "sstamp"; public const string SecurityStamp = "sstamp";
public const string Premium = "premium"; public const string Premium = "premium";
public const string Device = "device"; public const string Device = "device";
public const string OrganizationOwner = "orgowner"; public const string OrganizationOwner = "orgowner";
public const string OrganizationAdmin = "orgadmin"; public const string OrganizationAdmin = "orgadmin";
public const string OrganizationManager = "orgmanager"; public const string OrganizationManager = "orgmanager";
@ -14,6 +15,8 @@ public static class Claims
public const string ProviderAdmin = "providerprovideradmin"; public const string ProviderAdmin = "providerprovideradmin";
public const string ProviderServiceUser = "providerserviceuser"; public const string ProviderServiceUser = "providerserviceuser";
public const string SecretsManagerAccess = "accesssecretsmanager";
// Service Account // Service Account
public const string Organization = "organization"; public const string Organization = "organization";

View File

@ -0,0 +1,7 @@
namespace Bit.Core.Models.Api.OrganizationLicenses;
public class SelfHostedOrganizationLicenseRequestModel
{
public string LicenseKey { get; set; }
public string BillingSyncKey { get; set; }
}

View File

@ -8,6 +8,7 @@ public class OrganizationUserInvite
public IEnumerable<string> Emails { get; set; } public IEnumerable<string> Emails { get; set; }
public Enums.OrganizationUserType? Type { get; set; } public Enums.OrganizationUserType? Type { get; set; }
public bool AccessAll { get; set; } public bool AccessAll { get; set; }
public bool AccessSecretsManager { get; set; }
public Permissions Permissions { get; set; } public Permissions Permissions { get; set; }
public IEnumerable<CollectionAccessSelection> Collections { get; set; } public IEnumerable<CollectionAccessSelection> Collections { get; set; }
public IEnumerable<Guid> Groups { get; set; } public IEnumerable<Guid> Groups { get; set; }
@ -19,6 +20,7 @@ public class OrganizationUserInvite
Emails = requestModel.Emails; Emails = requestModel.Emails;
Type = requestModel.Type; Type = requestModel.Type;
AccessAll = requestModel.AccessAll; AccessAll = requestModel.AccessAll;
AccessSecretsManager = requestModel.AccessSecretsManager;
Collections = requestModel.Collections; Collections = requestModel.Collections;
Groups = requestModel.Groups; Groups = requestModel.Groups;
Permissions = requestModel.Permissions; Permissions = requestModel.Permissions;

View File

@ -1,9 +1,10 @@
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.OrganizationConnectionConfigs;
namespace Bit.Core.Models.Data.Organizations.OrganizationConnections; namespace Bit.Core.Models.Data.Organizations.OrganizationConnections;
public class OrganizationConnectionData<T> where T : new() public class OrganizationConnectionData<T> where T : IConnectionConfig
{ {
public Guid? Id { get; set; } public Guid? Id { get; set; }
public OrganizationConnectionType Type { get; set; } public OrganizationConnectionType Type { get; set; }

View File

@ -7,6 +7,7 @@ public class OrganizationUserInviteData
public IEnumerable<string> Emails { get; set; } public IEnumerable<string> Emails { get; set; }
public OrganizationUserType? Type { get; set; } public OrganizationUserType? Type { get; set; }
public bool AccessAll { get; set; } public bool AccessAll { get; set; }
public bool AccessSecretsManager { get; set; }
public IEnumerable<CollectionAccessSelection> Collections { get; set; } public IEnumerable<CollectionAccessSelection> Collections { get; set; }
public IEnumerable<Guid> Groups { get; set; } public IEnumerable<Guid> Groups { get; set; }
public Permissions Permissions { get; set; } public Permissions Permissions { get; set; }

View File

@ -41,4 +41,5 @@ public class OrganizationUserOrganizationDetails
public DateTime? FamilySponsorshipLastSyncDate { get; set; } public DateTime? FamilySponsorshipLastSyncDate { get; set; }
public DateTime? FamilySponsorshipValidUntil { get; set; } public DateTime? FamilySponsorshipValidUntil { get; set; }
public bool? FamilySponsorshipToDelete { get; set; } public bool? FamilySponsorshipToDelete { get; set; }
public bool AccessSecretsManager { get; set; }
} }

View File

@ -17,6 +17,7 @@ public class OrganizationUserUserDetails : IExternal, ITwoFactorProvidersUser
public OrganizationUserStatusType Status { get; set; } public OrganizationUserStatusType Status { get; set; }
public OrganizationUserType Type { get; set; } public OrganizationUserType Type { get; set; }
public bool AccessAll { get; set; } public bool AccessAll { get; set; }
public bool AccessSecretsManager { get; set; }
public string ExternalId { get; set; } public string ExternalId { get; set; }
public string SsoExternalId { get; set; } public string SsoExternalId { get; set; }
public string Permissions { get; set; } public string Permissions { get; set; }

View File

@ -34,4 +34,5 @@ public class ProviderUserOrganizationDetails
public Guid? ProviderId { get; set; } public Guid? ProviderId { get; set; }
public Guid? ProviderUserId { get; set; } public Guid? ProviderUserId { get; set; }
public string ProviderName { get; set; } public string ProviderName { get; set; }
public Enums.PlanType PlanType { get; set; }
} }

View File

@ -1,7 +1,20 @@
namespace Bit.Core.Models.OrganizationConnectionConfigs; namespace Bit.Core.Models.OrganizationConnectionConfigs;
public class BillingSyncConfig public class BillingSyncConfig : IConnectionConfig
{ {
public string BillingSyncKey { get; set; } public string BillingSyncKey { get; set; }
public Guid CloudOrganizationId { get; set; } public Guid CloudOrganizationId { get; set; }
public DateTime? LastLicenseSync { get; set; }
public bool Validate(out string exception)
{
if (string.IsNullOrWhiteSpace(BillingSyncKey))
{
exception = "Failed to get Billing Sync Key";
return false;
}
exception = "";
return true;
}
} }

View File

@ -0,0 +1,6 @@
namespace Bit.Core.Models.OrganizationConnectionConfigs;
public interface IConnectionConfig
{
bool Validate(out string exception);
}

View File

@ -3,9 +3,21 @@ using Bit.Core.Enums;
namespace Bit.Core.Models.OrganizationConnectionConfigs; namespace Bit.Core.Models.OrganizationConnectionConfigs;
public class ScimConfig public class ScimConfig : IConnectionConfig
{ {
public bool Enabled { get; set; } public bool Enabled { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public ScimProviderType? ScimProvider { get; set; } public ScimProviderType? ScimProvider { get; set; }
public bool Validate(out string exception)
{
if (!Enabled)
{
exception = "Scim Config is disabled";
return false;
}
exception = "";
return true;
}
} }

View File

@ -1,5 +1,6 @@
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Models.Data.Organizations.OrganizationConnections; using Bit.Core.Models.Data.Organizations.OrganizationConnections;
using Bit.Core.Models.OrganizationConnectionConfigs;
using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
@ -14,7 +15,7 @@ public class CreateOrganizationConnectionCommand : ICreateOrganizationConnection
_organizationConnectionRepository = organizationConnectionRepository; _organizationConnectionRepository = organizationConnectionRepository;
} }
public async Task<OrganizationConnection> CreateAsync<T>(OrganizationConnectionData<T> connectionData) where T : new() public async Task<OrganizationConnection> CreateAsync<T>(OrganizationConnectionData<T> connectionData) where T : IConnectionConfig
{ {
return await _organizationConnectionRepository.CreateAsync(connectionData.ToEntity()); return await _organizationConnectionRepository.CreateAsync(connectionData.ToEntity());
} }

View File

@ -1,9 +1,10 @@
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Models.Data.Organizations.OrganizationConnections; using Bit.Core.Models.Data.Organizations.OrganizationConnections;
using Bit.Core.Models.OrganizationConnectionConfigs;
namespace Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces; namespace Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
public interface ICreateOrganizationConnectionCommand public interface ICreateOrganizationConnectionCommand
{ {
Task<OrganizationConnection> CreateAsync<T>(OrganizationConnectionData<T> connectionData) where T : new(); Task<OrganizationConnection> CreateAsync<T>(OrganizationConnectionData<T> connectionData) where T : IConnectionConfig;
} }

View File

@ -1,9 +1,10 @@
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Models.Data.Organizations.OrganizationConnections; using Bit.Core.Models.Data.Organizations.OrganizationConnections;
using Bit.Core.Models.OrganizationConnectionConfigs;
namespace Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces; namespace Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
public interface IUpdateOrganizationConnectionCommand public interface IUpdateOrganizationConnectionCommand
{ {
Task<OrganizationConnection> UpdateAsync<T>(OrganizationConnectionData<T> connectionData) where T : new(); Task<OrganizationConnection> UpdateAsync<T>(OrganizationConnectionData<T> connectionData) where T : IConnectionConfig;
} }

View File

@ -1,6 +1,6 @@
using Bit.Core.Entities; using Bit.Core.Entities;
namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; namespace Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
public interface IValidateBillingSyncKeyCommand public interface IValidateBillingSyncKeyCommand
{ {

View File

@ -1,6 +1,7 @@
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Data.Organizations.OrganizationConnections; using Bit.Core.Models.Data.Organizations.OrganizationConnections;
using Bit.Core.Models.OrganizationConnectionConfigs;
using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
@ -15,7 +16,7 @@ public class UpdateOrganizationConnectionCommand : IUpdateOrganizationConnection
_organizationConnectionRepository = organizationConnectionRepository; _organizationConnectionRepository = organizationConnectionRepository;
} }
public async Task<OrganizationConnection> UpdateAsync<T>(OrganizationConnectionData<T> connectionData) where T : new() public async Task<OrganizationConnection> UpdateAsync<T>(OrganizationConnectionData<T> connectionData) where T : IConnectionConfig
{ {
if (!connectionData.Id.HasValue) if (!connectionData.Id.HasValue)
{ {

View File

@ -1,20 +1,17 @@
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
using Bit.Core.Repositories; using Bit.Core.Repositories;
namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud; namespace Bit.Core.OrganizationFeatures.OrganizationConnections;
public class ValidateBillingSyncKeyCommand : IValidateBillingSyncKeyCommand public class ValidateBillingSyncKeyCommand : IValidateBillingSyncKeyCommand
{ {
private readonly IOrganizationSponsorshipRepository _organizationSponsorshipRepository;
private readonly IOrganizationApiKeyRepository _apiKeyRepository; private readonly IOrganizationApiKeyRepository _apiKeyRepository;
public ValidateBillingSyncKeyCommand( public ValidateBillingSyncKeyCommand(
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
IOrganizationApiKeyRepository organizationApiKeyRepository) IOrganizationApiKeyRepository organizationApiKeyRepository)
{ {
_organizationSponsorshipRepository = organizationSponsorshipRepository;
_apiKeyRepository = organizationApiKeyRepository; _apiKeyRepository = organizationApiKeyRepository;
} }

View File

@ -0,0 +1,38 @@
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.Models.Business;
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
namespace Bit.Core.OrganizationFeatures.OrganizationLicenses;
public class CloudGetOrganizationLicenseQuery : ICloudGetOrganizationLicenseQuery
{
private readonly IInstallationRepository _installationRepository;
private readonly IPaymentService _paymentService;
private readonly ILicensingService _licensingService;
public CloudGetOrganizationLicenseQuery(
IInstallationRepository installationRepository,
IPaymentService paymentService,
ILicensingService licensingService)
{
_installationRepository = installationRepository;
_paymentService = paymentService;
_licensingService = licensingService;
}
public async Task<OrganizationLicense> GetLicenseAsync(Organization organization, Guid installationId,
int? version = null)
{
var installation = await _installationRepository.GetByIdAsync(installationId);
if (installation is not { Enabled: true })
{
throw new BadRequestException("Invalid installation id");
}
var subInfo = await _paymentService.GetSubscriptionAsync(organization);
return new OrganizationLicense(organization, subInfo, installationId, _licensingService, version);
}
}

View File

@ -0,0 +1,15 @@
using Bit.Core.Entities;
using Bit.Core.Models.Business;
namespace Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
public interface ICloudGetOrganizationLicenseQuery
{
Task<OrganizationLicense> GetLicenseAsync(Organization organization, Guid installationId,
int? version = null);
}
public interface ISelfHostedGetOrganizationLicenseQuery
{
Task<OrganizationLicense> GetLicenseAsync(Organization organization, OrganizationConnection billingSyncConnection);
}

View File

@ -0,0 +1,66 @@
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.Models.Api.OrganizationLicenses;
using Bit.Core.Models.Business;
using Bit.Core.Models.OrganizationConnectionConfigs;
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.Services;
using Bit.Core.Settings;
using Microsoft.Extensions.Logging;
namespace Bit.Core.OrganizationFeatures.OrganizationLicenses;
public class SelfHostedGetOrganizationLicenseQuery : BaseIdentityClientService, ISelfHostedGetOrganizationLicenseQuery
{
private readonly IGlobalSettings _globalSettings;
public SelfHostedGetOrganizationLicenseQuery(IHttpClientFactory httpFactory, IGlobalSettings globalSettings, ILogger<SelfHostedGetOrganizationLicenseQuery> logger, ICurrentContext currentContext)
: base(
httpFactory,
globalSettings.Installation.ApiUri,
globalSettings.Installation.IdentityUri,
"api.licensing",
$"installation.{globalSettings.Installation.Id}",
globalSettings.Installation.Key,
logger)
{
_globalSettings = globalSettings;
}
public async Task<OrganizationLicense> GetLicenseAsync(Organization organization, OrganizationConnection billingSyncConnection)
{
if (!_globalSettings.SelfHosted)
{
throw new BadRequestException("This action is only available for self-hosted.");
}
if (!_globalSettings.EnableCloudCommunication)
{
throw new BadRequestException("Cloud communication is disabled in global settings");
}
if (!billingSyncConnection.Validate<BillingSyncConfig>(out var exception))
{
throw new BadRequestException(exception);
}
var billingSyncConfig = billingSyncConnection.GetConfig<BillingSyncConfig>();
var cloudOrganizationId = billingSyncConfig.CloudOrganizationId;
var response = await SendAsync<SelfHostedOrganizationLicenseRequestModel, OrganizationLicense>(
HttpMethod.Get, $"licenses/organization/{cloudOrganizationId}", new SelfHostedOrganizationLicenseRequestModel()
{
BillingSyncKey = billingSyncConfig.BillingSyncKey,
LicenseKey = organization.LicenseKey,
}, true);
if (response == null)
{
_logger.LogDebug("Organization License sync failed for '{OrgId}'", organization.Id);
throw new BadRequestException("An error has occurred. Check your internet connection and ensure the billing token is correct.");
}
return response;
}
}

View File

@ -7,6 +7,8 @@ using Bit.Core.OrganizationFeatures.OrganizationCollections;
using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationConnections; using Bit.Core.OrganizationFeatures.OrganizationConnections;
using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationLicenses;
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
@ -32,6 +34,7 @@ public static class OrganizationServiceCollectionExtensions
services.AddOrganizationApiKeyCommandsQueries(); services.AddOrganizationApiKeyCommandsQueries();
services.AddOrganizationCollectionCommands(); services.AddOrganizationCollectionCommands();
services.AddOrganizationGroupCommands(); services.AddOrganizationGroupCommands();
services.AddOrganizationLicenseCommandQueries();
} }
private static void AddOrganizationConnectionCommands(this IServiceCollection services) private static void AddOrganizationConnectionCommands(this IServiceCollection services)
@ -85,6 +88,12 @@ public static class OrganizationServiceCollectionExtensions
services.AddScoped<IUpdateGroupCommand, UpdateGroupCommand>(); services.AddScoped<IUpdateGroupCommand, UpdateGroupCommand>();
} }
private static void AddOrganizationLicenseCommandQueries(this IServiceCollection services)
{
services.AddScoped<ICloudGetOrganizationLicenseQuery, CloudGetOrganizationLicenseQuery>();
services.AddScoped<ISelfHostedGetOrganizationLicenseQuery, SelfHostedGetOrganizationLicenseQuery>();
}
private static void AddTokenizers(this IServiceCollection services) private static void AddTokenizers(this IServiceCollection services)
{ {
services.AddSingleton<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>(serviceProvider => services.AddSingleton<IDataProtectorTokenFactory<OrganizationSponsorshipOfferTokenable>>(serviceProvider =>

View File

@ -48,20 +48,13 @@ public class SelfHostedSyncSponsorshipsCommand : BaseIdentityClientService, ISel
{ {
throw new BadRequestException("Failed to sync instance with cloud - Cloud communication is disabled in global settings"); throw new BadRequestException("Failed to sync instance with cloud - Cloud communication is disabled in global settings");
} }
if (!billingSyncConnection.Enabled)
if (!billingSyncConnection.Validate<BillingSyncConfig>(out var exception))
{ {
throw new BadRequestException($"Billing Sync Key disabled for organization {organizationId}"); throw new BadRequestException(exception);
}
if (string.IsNullOrWhiteSpace(billingSyncConnection.Config))
{
throw new BadRequestException($"No Billing Sync Key known for organization {organizationId}");
}
var billingSyncConfig = billingSyncConnection.GetConfig<BillingSyncConfig>();
if (billingSyncConfig == null || string.IsNullOrWhiteSpace(billingSyncConfig.BillingSyncKey))
{
throw new BadRequestException($"Failed to get Billing Sync Key for organization {organizationId}");
} }
var billingSyncConfig = billingSyncConnection.GetConfig<BillingSyncConfig>();
var organizationSponsorshipsDict = (await _organizationSponsorshipRepository.GetManyBySponsoringOrganizationAsync(organizationId)) var organizationSponsorshipsDict = (await _organizationSponsorshipRepository.GetManyBySponsoringOrganizationAsync(organizationId))
.ToDictionary(i => i.SponsoringOrganizationUserId); .ToDictionary(i => i.SponsoringOrganizationUserId);
if (!organizationSponsorshipsDict.Any()) if (!organizationSponsorshipsDict.Any())

View File

@ -11,7 +11,7 @@ public interface IProviderUserRepository : IRepository<ProviderUser, Guid>
Task<ICollection<ProviderUser>> GetManyByUserAsync(Guid userId); Task<ICollection<ProviderUser>> GetManyByUserAsync(Guid userId);
Task<ProviderUser> GetByProviderUserAsync(Guid providerId, Guid userId); Task<ProviderUser> GetByProviderUserAsync(Guid providerId, Guid userId);
Task<ICollection<ProviderUser>> GetManyByProviderAsync(Guid providerId, ProviderUserType? type = null); Task<ICollection<ProviderUser>> GetManyByProviderAsync(Guid providerId, ProviderUserType? type = null);
Task<ICollection<ProviderUserUserDetails>> GetManyDetailsByProviderAsync(Guid providerId); Task<ICollection<ProviderUserUserDetails>> GetManyDetailsByProviderAsync(Guid providerId, ProviderUserStatusType? status = null);
Task<ICollection<ProviderUserProviderDetails>> GetManyDetailsByUserAsync(Guid userId, Task<ICollection<ProviderUserProviderDetails>> GetManyDetailsByUserAsync(Guid userId,
ProviderUserStatusType? status = null); ProviderUserStatusType? status = null);
Task<IEnumerable<ProviderUserOrganizationDetails>> GetManyOrganizationDetailsByUserAsync(Guid userId, ProviderUserStatusType? status = null); Task<IEnumerable<ProviderUserOrganizationDetails>> GetManyOrganizationDetailsByUserAsync(Guid userId, ProviderUserStatusType? status = null);

View File

@ -4,5 +4,5 @@ namespace Bit.Core.SecretsManager.Commands.ServiceAccounts.Interfaces;
public interface ICreateServiceAccountCommand public interface ICreateServiceAccountCommand
{ {
Task<ServiceAccount> CreateAsync(ServiceAccount serviceAccount); Task<ServiceAccount> CreateAsync(ServiceAccount serviceAccount, Guid userId);
} }

View File

@ -8,7 +8,8 @@ public interface IAccessPolicyRepository
Task<List<BaseAccessPolicy>> CreateManyAsync(List<BaseAccessPolicy> baseAccessPolicies); Task<List<BaseAccessPolicy>> CreateManyAsync(List<BaseAccessPolicy> baseAccessPolicies);
Task<bool> AccessPolicyExists(BaseAccessPolicy baseAccessPolicy); Task<bool> AccessPolicyExists(BaseAccessPolicy baseAccessPolicy);
Task<BaseAccessPolicy?> GetByIdAsync(Guid id); Task<BaseAccessPolicy?> GetByIdAsync(Guid id);
Task<IEnumerable<BaseAccessPolicy>?> GetManyByProjectId(Guid id); Task<IEnumerable<BaseAccessPolicy>> GetManyByGrantedProjectIdAsync(Guid id);
Task<IEnumerable<BaseAccessPolicy>> GetManyByGrantedServiceAccountIdAsync(Guid id);
Task ReplaceAsync(BaseAccessPolicy baseAccessPolicy); Task ReplaceAsync(BaseAccessPolicy baseAccessPolicy);
Task DeleteAsync(Guid id); Task DeleteAsync(Guid id);
} }

View File

@ -56,9 +56,6 @@ public interface IOrganizationService
IEnumerable<Guid> organizationUserIds, Guid? deletingUserId); IEnumerable<Guid> organizationUserIds, Guid? deletingUserId);
Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds, Guid? loggedInUserId); Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds, Guid? loggedInUserId);
Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId); Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId);
Task<OrganizationLicense> GenerateLicenseAsync(Guid organizationId, Guid installationId);
Task<OrganizationLicense> GenerateLicenseAsync(Organization organization, Guid installationId,
int? version = null);
Task ImportAsync(Guid organizationId, Guid? importingUserId, IEnumerable<ImportedGroup> groups, Task ImportAsync(Guid organizationId, Guid? importingUserId, IEnumerable<ImportedGroup> groups,
IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds, IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds,
bool overwriteExisting); bool overwriteExisting);

View File

@ -2,6 +2,7 @@
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Enums.Provider;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
@ -43,6 +44,8 @@ public class OrganizationService : IOrganizationService
private readonly IOrganizationConnectionRepository _organizationConnectionRepository; private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly ILogger<OrganizationService> _logger; private readonly ILogger<OrganizationService> _logger;
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
private readonly IProviderUserRepository _providerUserRepository;
public OrganizationService( public OrganizationService(
IOrganizationRepository organizationRepository, IOrganizationRepository organizationRepository,
@ -69,7 +72,9 @@ public class OrganizationService : IOrganizationService
IOrganizationApiKeyRepository organizationApiKeyRepository, IOrganizationApiKeyRepository organizationApiKeyRepository,
IOrganizationConnectionRepository organizationConnectionRepository, IOrganizationConnectionRepository organizationConnectionRepository,
ICurrentContext currentContext, ICurrentContext currentContext,
ILogger<OrganizationService> logger) ILogger<OrganizationService> logger,
IProviderOrganizationRepository providerOrganizationRepository,
IProviderUserRepository providerUserRepository)
{ {
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
@ -96,6 +101,8 @@ public class OrganizationService : IOrganizationService
_organizationConnectionRepository = organizationConnectionRepository; _organizationConnectionRepository = organizationConnectionRepository;
_currentContext = currentContext; _currentContext = currentContext;
_logger = logger; _logger = logger;
_providerOrganizationRepository = providerOrganizationRepository;
_providerUserRepository = providerUserRepository;
} }
public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken, public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken,
@ -1236,6 +1243,7 @@ public class OrganizationService : IOrganizationService
Type = invite.Type.Value, Type = invite.Type.Value,
Status = OrganizationUserStatusType.Invited, Status = OrganizationUserStatusType.Invited,
AccessAll = invite.AccessAll, AccessAll = invite.AccessAll,
AccessSecretsManager = invite.AccessSecretsManager,
ExternalId = externalId, ExternalId = externalId,
CreationDate = DateTime.UtcNow, CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow, RevisionDate = DateTime.UtcNow,
@ -1637,8 +1645,19 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException(failureMessage); throw new BadRequestException(failureMessage);
} }
var ownerEmails = (await _organizationUserRepository.GetManyByMinimumRoleAsync(organization.Id, var providerOrg = await this._providerOrganizationRepository.GetByOrganizationId(organization.Id);
IEnumerable<string> ownerEmails;
if (providerOrg != null)
{
ownerEmails = (await _providerUserRepository.GetManyDetailsByProviderAsync(providerOrg.ProviderId, ProviderUserStatusType.Confirmed))
.Select(u => u.Email).Distinct();
}
else
{
ownerEmails = (await _organizationUserRepository.GetManyByMinimumRoleAsync(organization.Id,
OrganizationUserType.Owner)).Select(u => u.Email).Distinct(); OrganizationUserType.Owner)).Select(u => u.Email).Distinct();
}
var initialSeatCount = organization.Seats.Value; var initialSeatCount = organization.Seats.Value;
await AdjustSeatsAsync(organization, seatsToAdd, prorationDate, ownerEmails); await AdjustSeatsAsync(organization, seatsToAdd, prorationDate, ownerEmails);
@ -1910,30 +1929,6 @@ public class OrganizationService : IOrganizationService
EventType.OrganizationUser_ResetPassword_Enroll : EventType.OrganizationUser_ResetPassword_Withdraw); EventType.OrganizationUser_ResetPassword_Enroll : EventType.OrganizationUser_ResetPassword_Withdraw);
} }
public async Task<OrganizationLicense> GenerateLicenseAsync(Guid organizationId, Guid installationId)
{
var organization = await GetOrgById(organizationId);
return await GenerateLicenseAsync(organization, installationId);
}
public async Task<OrganizationLicense> GenerateLicenseAsync(Organization organization, Guid installationId,
int? version = null)
{
if (organization == null)
{
throw new NotFoundException();
}
var installation = await _installationRepository.GetByIdAsync(installationId);
if (installation == null || !installation.Enabled)
{
throw new BadRequestException("Invalid installation id");
}
var subInfo = await _paymentService.GetSubscriptionAsync(organization);
return new OrganizationLicense(organization, subInfo, installationId, _licensingService, version);
}
public async Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email, public async Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email,
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<CollectionAccessSelection> collections, OrganizationUserType type, bool accessAll, string externalId, IEnumerable<CollectionAccessSelection> collections,
IEnumerable<Guid> groups) IEnumerable<Guid> groups)

View File

@ -561,10 +561,13 @@ public class UserService : UserManager<User>, IUserService, IDisposable
return result; return result;
} }
var now = DateTime.UtcNow;
user.Key = key; user.Key = key;
user.Email = newEmail; user.Email = newEmail;
user.EmailVerified = true; user.EmailVerified = true;
user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow; user.RevisionDate = user.AccountRevisionDate = now;
user.LastEmailChangeDate = now;
await _userRepository.ReplaceAsync(user); await _userRepository.ReplaceAsync(user);
if (user.Gateway == GatewayType.Stripe) if (user.Gateway == GatewayType.Stripe)
@ -618,7 +621,9 @@ public class UserService : UserManager<User>, IUserService, IDisposable
return result; return result;
} }
user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow; var now = DateTime.UtcNow;
user.RevisionDate = user.AccountRevisionDate = now;
user.LastPasswordChangeDate = now;
user.Key = key; user.Key = key;
user.MasterPasswordHint = passwordHint; user.MasterPasswordHint = passwordHint;
@ -845,7 +850,9 @@ public class UserService : UserManager<User>, IUserService, IDisposable
return result; return result;
} }
user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow; var now = DateTime.UtcNow;
user.RevisionDate = user.AccountRevisionDate = now;
user.LastKdfChangeDate = now;
user.Key = key; user.Key = key;
user.Kdf = kdf; user.Kdf = kdf;
user.KdfIterations = kdfIterations; user.KdfIterations = kdfIterations;
@ -870,7 +877,9 @@ public class UserService : UserManager<User>, IUserService, IDisposable
if (await CheckPasswordAsync(user, masterPassword)) if (await CheckPasswordAsync(user, masterPassword))
{ {
user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow; var now = DateTime.UtcNow;
user.RevisionDate = user.AccountRevisionDate = now;
user.LastKeyRotationDate = now;
user.SecurityStamp = Guid.NewGuid().ToString(); user.SecurityStamp = Guid.NewGuid().ToString();
user.Key = key; user.Key = key;
user.PrivateKey = privateKey; user.PrivateKey = privateKey;

View File

@ -692,6 +692,15 @@ public static class CoreHelpers
default: default:
break; break;
} }
// Secrets Manager
foreach (var org in group)
{
if (org.AccessSecretsManager)
{
claims.Add(new KeyValuePair<string, string>(Claims.SecretsManagerAccess, org.Id.ToString()));
}
}
} }
} }

View File

@ -29,6 +29,9 @@ public class Startup
// Settings // Settings
var globalSettings = services.AddGlobalSettingsServices(Configuration, Environment); var globalSettings = services.AddGlobalSettingsServices(Configuration, Environment);
// Data Protection
services.AddCustomDataProtectionServices(Environment, globalSettings);
// Repositories // Repositories
services.AddDatabaseRepositories(globalSettings); services.AddDatabaseRepositories(globalSettings);

View File

@ -25,6 +25,7 @@ public class ApiResources
Claims.OrganizationCustom, Claims.OrganizationCustom,
Claims.ProviderAdmin, Claims.ProviderAdmin,
Claims.ProviderServiceUser, Claims.ProviderServiceUser,
Claims.SecretsManagerAccess,
}), }),
new(ApiScopes.Internal, new[] { JwtClaimTypes.Subject }), new(ApiScopes.Internal, new[] { JwtClaimTypes.Subject }),
new(ApiScopes.ApiPush, new[] { JwtClaimTypes.Subject }), new(ApiScopes.ApiPush, new[] { JwtClaimTypes.Subject }),

View File

@ -59,7 +59,7 @@ public static class DapperHelpers
public static DataTable ToTvp(this IEnumerable<OrganizationUser> orgUsers) public static DataTable ToTvp(this IEnumerable<OrganizationUser> orgUsers)
{ {
var table = new DataTable(); var table = new DataTable();
table.SetTypeName("[dbo].[OrganizationUserType]"); table.SetTypeName("[dbo].[OrganizationUserType2]");
var columnData = new List<(string name, Type type, Func<OrganizationUser, object> getter)> var columnData = new List<(string name, Type type, Func<OrganizationUser, object> getter)>
{ {
@ -76,6 +76,7 @@ public static class DapperHelpers
(nameof(OrganizationUser.RevisionDate), typeof(DateTime), ou => ou.RevisionDate), (nameof(OrganizationUser.RevisionDate), typeof(DateTime), ou => ou.RevisionDate),
(nameof(OrganizationUser.Permissions), typeof(string), ou => ou.Permissions), (nameof(OrganizationUser.Permissions), typeof(string), ou => ou.Permissions),
(nameof(OrganizationUser.ResetPasswordKey), typeof(string), ou => ou.ResetPasswordKey), (nameof(OrganizationUser.ResetPasswordKey), typeof(string), ou => ou.ResetPasswordKey),
(nameof(OrganizationUser.AccessSecretsManager), typeof(bool), ou => ou.AccessSecretsManager),
}; };
return orgUsers.BuildTable(table, columnData); return orgUsers.BuildTable(table, columnData);

View File

@ -321,6 +321,8 @@ public class CipherRepository : Repository<Cipher, Guid>, ICipherRepository
} }
cmd.Parameters.Add("@RevisionDate", SqlDbType.DateTime2).Value = user.RevisionDate; cmd.Parameters.Add("@RevisionDate", SqlDbType.DateTime2).Value = user.RevisionDate;
cmd.Parameters.Add("@AccountRevisionDate", SqlDbType.DateTime2).Value = user.AccountRevisionDate;
cmd.Parameters.Add("@LastKeyRotationDate", SqlDbType.DateTime2).Value = user.LastKeyRotationDate;
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
} }

View File

@ -405,7 +405,7 @@ public class OrganizationUserRepository : Repository<OrganizationUser, Guid>, IO
using (var connection = new SqlConnection(_marsConnectionString)) using (var connection = new SqlConnection(_marsConnectionString))
{ {
var results = await connection.ExecuteAsync( var results = await connection.ExecuteAsync(
$"[{Schema}].[{Table}_CreateMany]", $"[{Schema}].[{Table}_CreateMany2]",
new { OrganizationUsersInput = orgUsersTVP }, new { OrganizationUsersInput = orgUsersTVP },
commandType: CommandType.StoredProcedure); commandType: CommandType.StoredProcedure);
} }
@ -424,7 +424,7 @@ public class OrganizationUserRepository : Repository<OrganizationUser, Guid>, IO
using (var connection = new SqlConnection(_marsConnectionString)) using (var connection = new SqlConnection(_marsConnectionString))
{ {
var results = await connection.ExecuteAsync( var results = await connection.ExecuteAsync(
$"[{Schema}].[{Table}_UpdateMany]", $"[{Schema}].[{Table}_UpdateMany2]",
new { OrganizationUsersInput = orgUsersTVP }, new { OrganizationUsersInput = orgUsersTVP },
commandType: CommandType.StoredProcedure); commandType: CommandType.StoredProcedure);
} }

View File

@ -84,13 +84,13 @@ public class ProviderUserRepository : Repository<ProviderUser, Guid>, IProviderU
} }
} }
public async Task<ICollection<ProviderUserUserDetails>> GetManyDetailsByProviderAsync(Guid providerId) public async Task<ICollection<ProviderUserUserDetails>> GetManyDetailsByProviderAsync(Guid providerId, ProviderUserStatusType? status)
{ {
using (var connection = new SqlConnection(ConnectionString)) using (var connection = new SqlConnection(ConnectionString))
{ {
var results = await connection.QueryAsync<ProviderUserUserDetails>( var results = await connection.QueryAsync<ProviderUserUserDetails>(
"[dbo].[ProviderUserUserDetails_ReadByProviderId]", "[dbo].[ProviderUserUserDetails_ReadByProviderId]",
new { ProviderId = providerId }, new { ProviderId = providerId, Status = status },
commandType: CommandType.StoredProcedure); commandType: CommandType.StoredProcedure);
return results.ToList(); return results.ToList();

View File

@ -128,7 +128,9 @@ public abstract class BaseEntityFrameworkRepository
entity.SecurityStamp = user.SecurityStamp; entity.SecurityStamp = user.SecurityStamp;
entity.Key = user.Key; entity.Key = user.Key;
entity.PrivateKey = user.PrivateKey; entity.PrivateKey = user.PrivateKey;
entity.RevisionDate = DateTime.UtcNow; entity.LastKeyRotationDate = user.LastKeyRotationDate;
entity.AccountRevisionDate = user.AccountRevisionDate;
entity.RevisionDate = user.RevisionDate;
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync();
} }
} }

View File

@ -29,7 +29,7 @@ public class CipherRepository : Repository<Core.Entities.Cipher, Cipher, Guid>,
var dbContext = GetDatabaseContext(scope); var dbContext = GetDatabaseContext(scope);
if (cipher.OrganizationId.HasValue) if (cipher.OrganizationId.HasValue)
{ {
await dbContext.UserBumpAccountRevisionDateByCipherIdAsync(cipher.Id, cipher.OrganizationId); await dbContext.UserBumpAccountRevisionDateByCipherIdAsync(cipher.Id, cipher.OrganizationId.Value);
} }
else if (cipher.UserId.HasValue) else if (cipher.UserId.HasValue)
{ {
@ -59,7 +59,7 @@ public class CipherRepository : Repository<Core.Entities.Cipher, Cipher, Guid>,
await OrganizationUpdateStorage(cipherInfo.OrganizationId.Value); await OrganizationUpdateStorage(cipherInfo.OrganizationId.Value);
} }
await dbContext.UserBumpAccountRevisionDateByCipherIdAsync(cipher.Id, cipherInfo.OrganizationId); await dbContext.UserBumpAccountRevisionDateByCipherIdAsync(cipher.Id, cipherInfo.OrganizationId.Value);
} }
else if (cipherInfo?.UserId != null) else if (cipherInfo?.UserId != null)
{ {
@ -107,7 +107,16 @@ public class CipherRepository : Repository<Core.Entities.Cipher, Cipher, Guid>,
null; null;
var entity = Mapper.Map<Cipher>((Core.Entities.Cipher)cipher); var entity = Mapper.Map<Cipher>((Core.Entities.Cipher)cipher);
await dbContext.AddAsync(entity); await dbContext.AddAsync(entity);
await dbContext.UserBumpAccountRevisionDateByCipherIdAsync(cipher.Id, cipher.OrganizationId.GetValueOrDefault());
if (cipher.OrganizationId.HasValue)
{
await dbContext.UserBumpAccountRevisionDateByCipherIdAsync(cipher.Id, cipher.OrganizationId.Value);
}
else if (cipher.UserId.HasValue)
{
await dbContext.UserBumpAccountRevisionDateAsync(cipher.UserId.Value);
}
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync();
} }
return cipher; return cipher;
@ -458,7 +467,16 @@ public class CipherRepository : Repository<Core.Entities.Cipher, Cipher, Guid>,
} }
var mappedEntity = Mapper.Map<Cipher>((Core.Entities.Cipher)cipher); var mappedEntity = Mapper.Map<Cipher>((Core.Entities.Cipher)cipher);
dbContext.Entry(entity).CurrentValues.SetValues(mappedEntity); dbContext.Entry(entity).CurrentValues.SetValues(mappedEntity);
await dbContext.UserBumpAccountRevisionDateByCipherIdAsync(cipher.Id, cipher.OrganizationId.GetValueOrDefault());
if (cipher.OrganizationId.HasValue)
{
await dbContext.UserBumpAccountRevisionDateByCipherIdAsync(cipher.Id, cipher.OrganizationId.Value);
}
else if (cipher.UserId.HasValue)
{
await dbContext.UserBumpAccountRevisionDateAsync(cipher.UserId.Value);
}
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync();
} }
} }
@ -566,7 +584,15 @@ public class CipherRepository : Repository<Core.Entities.Cipher, Cipher, Guid>,
} }
} }
await dbContext.UserBumpAccountRevisionDateByCipherIdAsync(cipher.Id, cipher.OrganizationId.GetValueOrDefault()); if (cipher.OrganizationId.HasValue)
{
await dbContext.UserBumpAccountRevisionDateByCipherIdAsync(cipher.Id, cipher.OrganizationId.Value);
}
else if (cipher.UserId.HasValue)
{
await dbContext.UserBumpAccountRevisionDateAsync(cipher.UserId.Value);
}
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync();
return true; return true;
} }
@ -677,7 +703,7 @@ public class CipherRepository : Repository<Core.Entities.Cipher, Cipher, Guid>,
if (attachment.OrganizationId.HasValue) if (attachment.OrganizationId.HasValue)
{ {
await OrganizationUpdateStorage(cipher.OrganizationId.Value); await OrganizationUpdateStorage(cipher.OrganizationId.Value);
await dbContext.UserBumpAccountRevisionDateByCipherIdAsync(cipher.Id, cipher.OrganizationId); await dbContext.UserBumpAccountRevisionDateByCipherIdAsync(cipher.Id, cipher.OrganizationId.Value);
} }
else if (attachment.UserId.HasValue) else if (attachment.UserId.HasValue)
{ {

View File

@ -34,7 +34,7 @@ public static class DatabaseContextExtensions
UpdateUserRevisionDate(users); UpdateUserRevisionDate(users);
} }
public static async Task UserBumpAccountRevisionDateByCipherIdAsync(this DatabaseContext context, Guid cipherId, Guid? organizationId) public static async Task UserBumpAccountRevisionDateByCipherIdAsync(this DatabaseContext context, Guid cipherId, Guid organizationId)
{ {
var query = new UserBumpAccountRevisionDateByCipherIdQuery(cipherId, organizationId); var query = new UserBumpAccountRevisionDateByCipherIdQuery(cipherId, organizationId);
var users = await query.Run(context).ToListAsync(); var users = await query.Run(context).ToListAsync();

View File

@ -103,7 +103,7 @@ public class ProviderUserRepository :
return await query.FirstOrDefaultAsync(); return await query.FirstOrDefaultAsync();
} }
} }
public async Task<ICollection<ProviderUserUserDetails>> GetManyDetailsByProviderAsync(Guid providerId) public async Task<ICollection<ProviderUserUserDetails>> GetManyDetailsByProviderAsync(Guid providerId, ProviderUserStatusType? status)
{ {
using (var scope = ServiceScopeFactory.CreateScope()) using (var scope = ServiceScopeFactory.CreateScope())
{ {
@ -113,7 +113,9 @@ public class ProviderUserRepository :
on pu.UserId equals u.Id into u_g on pu.UserId equals u.Id into u_g
from u in u_g.DefaultIfEmpty() from u in u_g.DefaultIfEmpty()
select new { pu, u }; select new { pu, u };
var data = await view.Where(e => e.pu.ProviderId == providerId).Select(e => new ProviderUserUserDetails var data = await view
.Where(e => e.pu.ProviderId == providerId && (status == null || e.pu.Status == status))
.Select(e => new ProviderUserUserDetails
{ {
Id = e.pu.Id, Id = e.pu.Id,
UserId = e.pu.UserId, UserId = e.pu.UserId,

View File

@ -37,6 +37,7 @@ public class OrganizationUserOrganizationDetailsViewQuery : IQuery<OrganizationU
Use2fa = o.Use2fa, Use2fa = o.Use2fa,
UseApi = o.UseApi, UseApi = o.UseApi,
UseResetPassword = o.UseResetPassword, UseResetPassword = o.UseResetPassword,
UseSecretsManager = o.UseSecretsManager,
SelfHost = o.SelfHost, SelfHost = o.SelfHost,
UsersGetPremium = o.UsersGetPremium, UsersGetPremium = o.UsersGetPremium,
UseCustomPermissions = o.UseCustomPermissions, UseCustomPermissions = o.UseCustomPermissions,
@ -58,7 +59,8 @@ public class OrganizationUserOrganizationDetailsViewQuery : IQuery<OrganizationU
FamilySponsorshipFriendlyName = os.FriendlyName, FamilySponsorshipFriendlyName = os.FriendlyName,
FamilySponsorshipLastSyncDate = os.LastSyncDate, FamilySponsorshipLastSyncDate = os.LastSyncDate,
FamilySponsorshipToDelete = os.ToDelete, FamilySponsorshipToDelete = os.ToDelete,
FamilySponsorshipValidUntil = os.ValidUntil FamilySponsorshipValidUntil = os.ValidUntil,
AccessSecretsManager = ou.AccessSecretsManager,
}; };
return query; return query;
} }

View File

@ -29,6 +29,7 @@ public class OrganizationUserUserDetailsViewQuery : IQuery<OrganizationUserUserD
Permissions = x.ou.Permissions, Permissions = x.ou.Permissions,
ResetPasswordKey = x.ou.ResetPasswordKey, ResetPasswordKey = x.ou.ResetPasswordKey,
UsesKeyConnector = x.u != null && x.u.UsesKeyConnector, UsesKeyConnector = x.u != null && x.u.UsesKeyConnector,
AccessSecretsManager = x.ou.AccessSecretsManager,
}); });
} }
} }

View File

@ -41,6 +41,7 @@ public class ProviderUserOrganizationDetailsViewQuery : IQuery<ProviderUserOrgan
PrivateKey = x.o.PrivateKey, PrivateKey = x.o.PrivateKey,
ProviderId = x.p.Id, ProviderId = x.p.Id,
ProviderName = x.p.Name, ProviderName = x.p.Name,
PlanType = x.o.PlanType
}); });
} }
} }

View File

@ -1,5 +1,4 @@
using Bit.Core.Entities; using Bit.Core.Enums;
using Bit.Core.Enums;
using User = Bit.Infrastructure.EntityFramework.Models.User; using User = Bit.Infrastructure.EntityFramework.Models.User;
namespace Bit.Infrastructure.EntityFramework.Repositories.Queries; namespace Bit.Infrastructure.EntityFramework.Repositories.Queries;
@ -7,15 +6,9 @@ namespace Bit.Infrastructure.EntityFramework.Repositories.Queries;
public class UserBumpAccountRevisionDateByCipherIdQuery : IQuery<User> public class UserBumpAccountRevisionDateByCipherIdQuery : IQuery<User>
{ {
private readonly Guid _cipherId; private readonly Guid _cipherId;
private readonly Guid? _organizationId; private readonly Guid _organizationId;
public UserBumpAccountRevisionDateByCipherIdQuery(Cipher cipher) public UserBumpAccountRevisionDateByCipherIdQuery(Guid cipherId, Guid organizationId)
{
_cipherId = cipher.Id;
_organizationId = cipher.OrganizationId;
}
public UserBumpAccountRevisionDateByCipherIdQuery(Guid cipherId, Guid? organizationId)
{ {
_cipherId = cipherId; _cipherId = cipherId;
_organizationId = organizationId; _organizationId = organizationId;

View File

@ -13,7 +13,7 @@ public class HeartbeatHostedService : IHostedService, IDisposable
private CancellationTokenSource _cts; private CancellationTokenSource _cts;
public HeartbeatHostedService( public HeartbeatHostedService(
ILogger<AzureQueueHostedService> logger, ILogger<HeartbeatHostedService> logger,
IHubContext<NotificationsHub> hubContext, IHubContext<NotificationsHub> hubContext,
GlobalSettings globalSettings) GlobalSettings globalSettings)
{ {
@ -49,7 +49,7 @@ public class HeartbeatHostedService : IHostedService, IDisposable
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
await _hubContext.Clients.All.SendAsync("Heartbeat"); await _hubContext.Clients.All.SendAsync("Heartbeat");
await Task.Delay(120000); await Task.Delay(120000, cancellationToken);
} }
_logger.LogWarning("Done with heartbeat."); _logger.LogWarning("Done with heartbeat.");
} }

View File

@ -238,6 +238,7 @@
<Build Include="dbo\Stored Procedures\OrganizationUser_Activate.sql" /> <Build Include="dbo\Stored Procedures\OrganizationUser_Activate.sql" />
<Build Include="dbo\Stored Procedures\OrganizationUser_Create.sql" /> <Build Include="dbo\Stored Procedures\OrganizationUser_Create.sql" />
<Build Include="dbo\Stored Procedures\OrganizationUser_CreateMany.sql" /> <Build Include="dbo\Stored Procedures\OrganizationUser_CreateMany.sql" />
<Build Include="dbo\Stored Procedures\OrganizationUser_CreateMany2.sql" />
<Build Include="dbo\Stored Procedures\OrganizationUser_CreateWithCollections.sql" /> <Build Include="dbo\Stored Procedures\OrganizationUser_CreateWithCollections.sql" />
<Build Include="dbo\Stored Procedures\OrganizationUser_Deactivate.sql" /> <Build Include="dbo\Stored Procedures\OrganizationUser_Deactivate.sql" />
<Build Include="dbo\Stored Procedures\OrganizationUser_DeleteById.sql" /> <Build Include="dbo\Stored Procedures\OrganizationUser_DeleteById.sql" />
@ -258,6 +259,7 @@
<Build Include="dbo\Stored Procedures\OrganizationUser_SelectKnownEmails.sql" /> <Build Include="dbo\Stored Procedures\OrganizationUser_SelectKnownEmails.sql" />
<Build Include="dbo\Stored Procedures\OrganizationUser_Update.sql" /> <Build Include="dbo\Stored Procedures\OrganizationUser_Update.sql" />
<Build Include="dbo\Stored Procedures\OrganizationUser_UpdateMany.sql" /> <Build Include="dbo\Stored Procedures\OrganizationUser_UpdateMany.sql" />
<Build Include="dbo\Stored Procedures\OrganizationUser_UpdateMany2.sql" />
<Build Include="dbo\Stored Procedures\OrganizationUser_UpdateWithCollections.sql" /> <Build Include="dbo\Stored Procedures\OrganizationUser_UpdateWithCollections.sql" />
<Build Include="dbo\Stored Procedures\Organization_Create.sql" /> <Build Include="dbo\Stored Procedures\Organization_Create.sql" />
<Build Include="dbo\Stored Procedures\Organization_DeleteById.sql" /> <Build Include="dbo\Stored Procedures\Organization_DeleteById.sql" />
@ -400,6 +402,7 @@
<Build Include="dbo\User Defined Types\GuidIdArray.sql" /> <Build Include="dbo\User Defined Types\GuidIdArray.sql" />
<Build Include="dbo\User Defined Types\OrganizationSponsorshipType.sql" /> <Build Include="dbo\User Defined Types\OrganizationSponsorshipType.sql" />
<Build Include="dbo\User Defined Types\OrganizationUserType.sql" /> <Build Include="dbo\User Defined Types\OrganizationUserType.sql" />
<Build Include="dbo\User Defined Types\OrganizationUserType2.sql" />
<Build Include="dbo\User Defined Types\SelectionReadOnlyArray.sql" /> <Build Include="dbo\User Defined Types\SelectionReadOnlyArray.sql" />
<Build Include="dbo\User Defined Types\TwoGuidIdArray.sql" /> <Build Include="dbo\User Defined Types\TwoGuidIdArray.sql" />
<Build Include="dbo\Views\AuthRequestView.sql" /> <Build Include="dbo\Views\AuthRequestView.sql" />

View File

@ -11,7 +11,8 @@
@CreationDate DATETIME2(7), @CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7), @RevisionDate DATETIME2(7),
@Permissions NVARCHAR(MAX), @Permissions NVARCHAR(MAX),
@ResetPasswordKey VARCHAR(MAX) @ResetPasswordKey VARCHAR(MAX),
@AccessSecretsManager BIT = 0
AS AS
BEGIN BEGIN
SET NOCOUNT ON SET NOCOUNT ON
@ -30,7 +31,8 @@ BEGIN
[CreationDate], [CreationDate],
[RevisionDate], [RevisionDate],
[Permissions], [Permissions],
[ResetPasswordKey] [ResetPasswordKey],
[AccessSecretsManager]
) )
VALUES VALUES
( (
@ -46,6 +48,7 @@ BEGIN
@CreationDate, @CreationDate,
@RevisionDate, @RevisionDate,
@Permissions, @Permissions,
@ResetPasswordKey @ResetPasswordKey,
@AccessSecretsManager
) )
END END

View File

@ -0,0 +1,42 @@
CREATE PROCEDURE [dbo].[OrganizationUser_CreateMany2]
@OrganizationUsersInput [dbo].[OrganizationUserType2] READONLY
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[OrganizationUser]
(
[Id],
[OrganizationId],
[UserId],
[Email],
[Key],
[Status],
[Type],
[AccessAll],
[ExternalId],
[CreationDate],
[RevisionDate],
[Permissions],
[ResetPasswordKey],
[AccessSecretsManager]
)
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],
OU.[AccessSecretsManager]
FROM
@OrganizationUsersInput OU
END
GO

View File

@ -12,12 +12,13 @@
@RevisionDate DATETIME2(7), @RevisionDate DATETIME2(7),
@Permissions NVARCHAR(MAX), @Permissions NVARCHAR(MAX),
@ResetPasswordKey VARCHAR(MAX), @ResetPasswordKey VARCHAR(MAX),
@Collections AS [dbo].[SelectionReadOnlyArray] READONLY @Collections AS [dbo].[SelectionReadOnlyArray] READONLY,
@AccessSecretsManager BIT = 0
AS AS
BEGIN BEGIN
SET NOCOUNT ON SET NOCOUNT ON
EXEC [dbo].[OrganizationUser_Create] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate, @Permissions, @ResetPasswordKey EXEC [dbo].[OrganizationUser_Create] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate, @Permissions, @ResetPasswordKey, @AccessSecretsManager
;WITH [AvailableCollectionsCTE] AS( ;WITH [AvailableCollectionsCTE] AS(
SELECT SELECT

View File

@ -11,7 +11,8 @@
@CreationDate DATETIME2(7), @CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7), @RevisionDate DATETIME2(7),
@Permissions NVARCHAR(MAX), @Permissions NVARCHAR(MAX),
@ResetPasswordKey VARCHAR(MAX) @ResetPasswordKey VARCHAR(MAX),
@AccessSecretsManager BIT = 0
AS AS
BEGIN BEGIN
SET NOCOUNT ON SET NOCOUNT ON
@ -30,7 +31,8 @@ BEGIN
[CreationDate] = @CreationDate, [CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate, [RevisionDate] = @RevisionDate,
[Permissions] = @Permissions, [Permissions] = @Permissions,
[ResetPasswordKey] = @ResetPasswordKey [ResetPasswordKey] = @ResetPasswordKey,
[AccessSecretsManager] = @AccessSecretsManager
WHERE WHERE
[Id] = @Id [Id] = @Id

View File

@ -0,0 +1,34 @@
CREATE PROCEDURE [dbo].[OrganizationUser_UpdateMany2]
@OrganizationUsersInput [dbo].[OrganizationUserType2] 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],
[AccessSecretsManager] = OUI.[AccessSecretsManager]
FROM
[dbo].[OrganizationUser] OU
INNER JOIN
@OrganizationUsersInput OUI ON OU.Id = OUI.Id
EXEC [dbo].[User_BumpManyAccountRevisionDates]
(
SELECT UserId
FROM @OrganizationUsersInput
)
END
GO

View File

@ -12,12 +12,13 @@
@RevisionDate DATETIME2(7), @RevisionDate DATETIME2(7),
@Permissions NVARCHAR(MAX), @Permissions NVARCHAR(MAX),
@ResetPasswordKey VARCHAR(MAX), @ResetPasswordKey VARCHAR(MAX),
@Collections AS [dbo].[SelectionReadOnlyArray] READONLY @Collections AS [dbo].[SelectionReadOnlyArray] READONLY,
@AccessSecretsManager BIT = 0
AS AS
BEGIN BEGIN
SET NOCOUNT ON SET NOCOUNT ON
EXEC [dbo].[OrganizationUser_Update] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate, @Permissions, @ResetPasswordKey EXEC [dbo].[OrganizationUser_Update] @Id, @OrganizationId, @UserId, @Email, @Key, @Status, @Type, @AccessAll, @ExternalId, @CreationDate, @RevisionDate, @Permissions, @ResetPasswordKey, @AccessSecretsManager
-- Update -- Update
UPDATE UPDATE
[Target] [Target]

View File

@ -1,5 +1,6 @@
CREATE PROCEDURE [dbo].[ProviderUserUserDetails_ReadByProviderId] CREATE PROCEDURE [dbo].[ProviderUserUserDetails_ReadByProviderId]
@ProviderId UNIQUEIDENTIFIER @ProviderId UNIQUEIDENTIFIER,
@Status TINYINT = NULL
AS AS
BEGIN BEGIN
SET NOCOUNT ON SET NOCOUNT ON
@ -10,4 +11,5 @@ BEGIN
[dbo].[ProviderUserUserDetailsView] [dbo].[ProviderUserUserDetailsView]
WHERE WHERE
[ProviderId] = @ProviderId [ProviderId] = @ProviderId
AND [Status] = COALESCE(@Status, [Status])
END END

View File

@ -37,7 +37,11 @@
@FailedLoginCount INT = 0, @FailedLoginCount INT = 0,
@LastFailedLoginDate DATETIME2(7), @LastFailedLoginDate DATETIME2(7),
@UnknownDeviceVerificationEnabled BIT = 1, @UnknownDeviceVerificationEnabled BIT = 1,
@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 AS
BEGIN BEGIN
SET NOCOUNT ON SET NOCOUNT ON
@ -82,7 +86,11 @@ BEGIN
[UnknownDeviceVerificationEnabled], [UnknownDeviceVerificationEnabled],
[AvatarColor], [AvatarColor],
[KdfMemory], [KdfMemory],
[KdfParallelism] [KdfParallelism],
[LastPasswordChangeDate],
[LastKdfChangeDate],
[LastKeyRotationDate],
[LastEmailChangeDate]
) )
VALUES VALUES
( (
@ -124,6 +132,10 @@ BEGIN
@UnknownDeviceVerificationEnabled, @UnknownDeviceVerificationEnabled,
@AvatarColor, @AvatarColor,
@KdfMemory, @KdfMemory,
@KdfParallelism @KdfParallelism,
@LastPasswordChangeDate,
@LastKdfChangeDate,
@LastKeyRotationDate,
@LastEmailChangeDate
) )
END END

View File

@ -37,7 +37,11 @@
@FailedLoginCount INT, @FailedLoginCount INT,
@LastFailedLoginDate DATETIME2(7), @LastFailedLoginDate DATETIME2(7),
@UnknownDeviceVerificationEnabled BIT = 1, @UnknownDeviceVerificationEnabled BIT = 1,
@AvatarColor VARCHAR(7) @AvatarColor VARCHAR(7),
@LastPasswordChangeDate DATETIME2(7) = NULL,
@LastKdfChangeDate DATETIME2(7) = NULL,
@LastKeyRotationDate DATETIME2(7) = NULL,
@LastEmailChangeDate DATETIME2(7) = NULL
AS AS
BEGIN BEGIN
SET NOCOUNT ON SET NOCOUNT ON
@ -82,7 +86,11 @@ BEGIN
[FailedLoginCount] = @FailedLoginCount, [FailedLoginCount] = @FailedLoginCount,
[LastFailedLoginDate] = @LastFailedLoginDate, [LastFailedLoginDate] = @LastFailedLoginDate,
[UnknownDeviceVerificationEnabled] = @UnknownDeviceVerificationEnabled, [UnknownDeviceVerificationEnabled] = @UnknownDeviceVerificationEnabled,
[AvatarColor] = @AvatarColor [AvatarColor] = @AvatarColor,
[LastPasswordChangeDate] = @LastPasswordChangeDate,
[LastKdfChangeDate] = @LastKdfChangeDate,
[LastKeyRotationDate] = @LastKeyRotationDate,
[LastEmailChangeDate] = @LastEmailChangeDate
WHERE WHERE
[Id] = @Id [Id] = @Id
END END

View File

@ -3,7 +3,9 @@
@SecurityStamp NVARCHAR(50), @SecurityStamp NVARCHAR(50),
@Key NVARCHAR(MAX), @Key NVARCHAR(MAX),
@PrivateKey VARCHAR(MAX), @PrivateKey VARCHAR(MAX),
@RevisionDate DATETIME2(7) @RevisionDate DATETIME2(7),
@AccountRevisionDate DATETIME2(7) = NULL,
@LastKeyRotationDate DATETIME2(7) = NULL
AS AS
BEGIN BEGIN
SET NOCOUNT ON SET NOCOUNT ON
@ -15,7 +17,8 @@ BEGIN
[Key] = @Key, [Key] = @Key,
[PrivateKey] = @PrivateKey, [PrivateKey] = @PrivateKey,
[RevisionDate] = @RevisionDate, [RevisionDate] = @RevisionDate,
[AccountRevisionDate] = @RevisionDate [AccountRevisionDate] = ISNULL(@AccountRevisionDate, @RevisionDate),
[LastKeyRotationDate] = @LastKeyRotationDate
WHERE WHERE
[Id] = @Id [Id] = @Id
END END

View File

@ -12,6 +12,7 @@
[CreationDate] DATETIME2 (7) NOT NULL, [CreationDate] DATETIME2 (7) NOT NULL,
[RevisionDate] DATETIME2 (7) NOT NULL, [RevisionDate] DATETIME2 (7) NOT NULL,
[Permissions] NVARCHAR (MAX) NULL, [Permissions] NVARCHAR (MAX) NULL,
[AccessSecretsManager] BIT NOT NULL CONSTRAINT [DF_OrganizationUser_SecretsManager] DEFAULT (0),
CONSTRAINT [PK_OrganizationUser] PRIMARY KEY CLUSTERED ([Id] ASC), CONSTRAINT [PK_OrganizationUser] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_OrganizationUser_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) ON DELETE CASCADE, CONSTRAINT [FK_OrganizationUser_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) ON DELETE CASCADE,
CONSTRAINT [FK_OrganizationUser_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) CONSTRAINT [FK_OrganizationUser_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id])

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