mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 15:42:48 -05:00
SM-1146: Secrets Manager total counts (#4200)
* SM-1146: SM Organization Counts for Projects, Secrets, Machine Accounts * SM-1146: Project total counts * SM-1146: models object renames * SM-1146: Service Account total counts * SM-1146: Unit test coverage for counts controller * SM-1146: Counts controller simplification, UT update * SM-1146: Service Account total counts from Service Account auth user * SM-1146: Integration Tests for total counts controller * SM-1146: Explicitly denying access for Service Accounts * SM-1146: Fix broken ProjectsController integration test * SM-1146: Integration tests for counts controller * SM-1146: Explicitly denying access for Service Accounts cleanup * SM-1146: Test cleanup * SM-1146: PR review comments fix * SM-1146: People, Service Accounts positive count on write access * Update bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/ProjectRepository.cs Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> --------- Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>
This commit is contained in:
119
src/Api/SecretsManager/Controllers/CountsController.cs
Normal file
119
src/Api/SecretsManager/Controllers/CountsController.cs
Normal file
@ -0,0 +1,119 @@
|
||||
#nullable enable
|
||||
using Bit.Api.SecretsManager.Models.Response;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.SecretsManager.Queries.Interfaces;
|
||||
using Bit.Core.SecretsManager.Repositories;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Controllers;
|
||||
|
||||
[Authorize("secrets")]
|
||||
public class CountsController : Controller
|
||||
{
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IAccessClientQuery _accessClientQuery;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly ISecretRepository _secretRepository;
|
||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||
|
||||
public CountsController(
|
||||
ICurrentContext currentContext,
|
||||
IAccessClientQuery accessClientQuery,
|
||||
IProjectRepository projectRepository,
|
||||
ISecretRepository secretRepository,
|
||||
IServiceAccountRepository serviceAccountRepository)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_accessClientQuery = accessClientQuery;
|
||||
_projectRepository = projectRepository;
|
||||
_secretRepository = secretRepository;
|
||||
_serviceAccountRepository = serviceAccountRepository;
|
||||
}
|
||||
|
||||
[HttpGet("organizations/{organizationId}/sm-counts")]
|
||||
public async Task<OrganizationCountsResponseModel> GetByOrganizationAsync([FromRoute] Guid organizationId)
|
||||
{
|
||||
var (accessType, userId) = await GetAccessClientAsync(organizationId);
|
||||
|
||||
var projectsCountTask = _projectRepository.GetProjectCountByOrganizationIdAsync(organizationId,
|
||||
userId, accessType);
|
||||
|
||||
var secretsCountTask = _secretRepository.GetSecretsCountByOrganizationIdAsync(organizationId,
|
||||
userId, accessType);
|
||||
|
||||
var serviceAccountsCountsTask = _serviceAccountRepository.GetServiceAccountCountByOrganizationIdAsync(
|
||||
organizationId, userId, accessType);
|
||||
|
||||
var counts = await Task.WhenAll(projectsCountTask, secretsCountTask, serviceAccountsCountsTask);
|
||||
|
||||
return new OrganizationCountsResponseModel
|
||||
{
|
||||
Projects = counts[0],
|
||||
Secrets = counts[1],
|
||||
ServiceAccounts = counts[2]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("projects/{projectId}/sm-counts")]
|
||||
public async Task<ProjectCountsResponseModel> GetByProjectAsync([FromRoute] Guid projectId)
|
||||
{
|
||||
var project = await _projectRepository.GetByIdAsync(projectId);
|
||||
if (project == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var (accessType, userId) = await GetAccessClientAsync(project.OrganizationId);
|
||||
|
||||
var projectsCounts = await _projectRepository.GetProjectCountsByIdAsync(projectId, userId, accessType);
|
||||
|
||||
return new ProjectCountsResponseModel
|
||||
{
|
||||
Secrets = projectsCounts.Secrets,
|
||||
People = projectsCounts.People,
|
||||
ServiceAccounts = projectsCounts.ServiceAccounts
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("service-accounts/{serviceAccountId}/sm-counts")]
|
||||
public async Task<ServiceAccountCountsResponseModel> GetByServiceAccountAsync([FromRoute] Guid serviceAccountId)
|
||||
{
|
||||
var serviceAccount = await _serviceAccountRepository.GetByIdAsync(serviceAccountId);
|
||||
if (serviceAccount == null)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var (accessType, userId) = await GetAccessClientAsync(serviceAccount.OrganizationId);
|
||||
|
||||
var serviceAccountCounts =
|
||||
await _serviceAccountRepository.GetServiceAccountCountsByIdAsync(serviceAccountId, userId, accessType);
|
||||
|
||||
return new ServiceAccountCountsResponseModel
|
||||
{
|
||||
Projects = serviceAccountCounts.Projects,
|
||||
People = serviceAccountCounts.People,
|
||||
AccessTokens = serviceAccountCounts.AccessTokens
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<(AccessClientType, Guid)> GetAccessClientAsync(Guid organizationId)
|
||||
{
|
||||
if (!_currentContext.AccessSecretsManager(organizationId))
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var (accessType, userId) = await _accessClientQuery.GetAccessClientAsync(User, organizationId);
|
||||
if (accessType == AccessClientType.ServiceAccount)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return (accessType, userId);
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Models.Api;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Response;
|
||||
|
||||
public class OrganizationCountsResponseModel() : ResponseModel(_objectName)
|
||||
{
|
||||
private const string _objectName = "organizationCounts";
|
||||
|
||||
public int Projects { get; set; }
|
||||
|
||||
public int Secrets { get; set; }
|
||||
|
||||
public int ServiceAccounts { get; set; }
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Models.Api;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Response;
|
||||
|
||||
public class ProjectCountsResponseModel() : ResponseModel(_objectName)
|
||||
{
|
||||
private const string _objectName = "projectCounts";
|
||||
|
||||
public int Secrets { get; set; }
|
||||
|
||||
public int People { get; set; }
|
||||
|
||||
public int ServiceAccounts { get; set; }
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Models.Api;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Response;
|
||||
|
||||
public class ServiceAccountCountsResponseModel() : ResponseModel(_objectName)
|
||||
{
|
||||
private const string _objectName = "serviceAccountCounts";
|
||||
|
||||
public int Projects { get; set; }
|
||||
|
||||
public int People { get; set; }
|
||||
|
||||
public int AccessTokens { get; set; }
|
||||
}
|
Reference in New Issue
Block a user