1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-04 01:22:50 -05:00

[SM-1150] Add secret sync endpoint (#3906)

* Add SecretsSyncQuery

* Add SecretsSync to controller

* Add unit tests

* Add integration tests

* update repo layer
This commit is contained in:
Thomas Avery
2024-04-25 10:34:08 -05:00
committed by GitHub
parent f7aa56b324
commit a7b992d424
16 changed files with 711 additions and 138 deletions

View File

@ -9,6 +9,9 @@ using Bit.Core.Repositories;
using Bit.Core.SecretsManager.AuthorizationRequirements;
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Models.Data;
using Bit.Core.SecretsManager.Queries.Interfaces;
using Bit.Core.SecretsManager.Queries.Secrets.Interfaces;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Services;
using Bit.Core.Tools.Enums;
@ -29,6 +32,8 @@ public class SecretsController : Controller
private readonly ICreateSecretCommand _createSecretCommand;
private readonly IUpdateSecretCommand _updateSecretCommand;
private readonly IDeleteSecretCommand _deleteSecretCommand;
private readonly IAccessClientQuery _accessClientQuery;
private readonly ISecretsSyncQuery _secretsSyncQuery;
private readonly IUserService _userService;
private readonly IEventService _eventService;
private readonly IReferenceEventService _referenceEventService;
@ -42,6 +47,8 @@ public class SecretsController : Controller
ICreateSecretCommand createSecretCommand,
IUpdateSecretCommand updateSecretCommand,
IDeleteSecretCommand deleteSecretCommand,
IAccessClientQuery accessClientQuery,
ISecretsSyncQuery secretsSyncQuery,
IUserService userService,
IEventService eventService,
IReferenceEventService referenceEventService,
@ -54,6 +61,8 @@ public class SecretsController : Controller
_createSecretCommand = createSecretCommand;
_updateSecretCommand = updateSecretCommand;
_deleteSecretCommand = deleteSecretCommand;
_accessClientQuery = accessClientQuery;
_secretsSyncQuery = secretsSyncQuery;
_userService = userService;
_eventService = eventService;
_referenceEventService = referenceEventService;
@ -73,7 +82,7 @@ public class SecretsController : Controller
var orgAdmin = await _currentContext.OrganizationAdmin(organizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId, userId, accessClient);
var secrets = await _secretRepository.GetManyDetailsByOrganizationIdAsync(organizationId, userId, accessClient);
return new SecretWithProjectsListResponseModel(secrets);
}
@ -139,7 +148,7 @@ public class SecretsController : Controller
var orgAdmin = await _currentContext.OrganizationAdmin(project.OrganizationId);
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.ClientType, orgAdmin);
var secrets = await _secretRepository.GetManyByProjectIdAsync(projectId, userId, accessClient);
var secrets = await _secretRepository.GetManyDetailsByProjectIdAsync(projectId, userId, accessClient);
return new SecretWithProjectsListResponseModel(secrets);
}
@ -246,4 +255,35 @@ public class SecretsController : Controller
var responses = secrets.Select(s => new BaseSecretResponseModel(s));
return new ListResponseModel<BaseSecretResponseModel>(responses);
}
[HttpGet("/organizations/{organizationId}/secrets/sync")]
public async Task<SecretsSyncResponseModel> GetSecretsSyncAsync([FromRoute] Guid organizationId,
[FromQuery] DateTime? lastSyncedDate = null)
{
if (lastSyncedDate.HasValue && lastSyncedDate.Value > DateTime.UtcNow)
{
throw new BadRequestException("Last synced date must be in the past.");
}
if (!_currentContext.AccessSecretsManager(organizationId))
{
throw new NotFoundException();
}
var (accessClient, serviceAccountId) = await _accessClientQuery.GetAccessClientAsync(User, organizationId);
if (accessClient != AccessClientType.ServiceAccount)
{
throw new BadRequestException("Only service accounts can sync secrets.");
}
var syncRequest = new SecretsSyncRequest
{
AccessClientType = accessClient,
OrganizationId = organizationId,
ServiceAccountId = serviceAccountId,
LastSyncedDate = lastSyncedDate
};
var (hasChanges, secrets) = await _secretsSyncQuery.GetAsync(syncRequest);
return new SecretsSyncResponseModel(hasChanges, secrets);
}
}

View File

@ -44,7 +44,7 @@ public class SecretsManagerPortingController : Controller
var userId = _userService.GetProperUserId(User).Value;
var projects = await _projectRepository.GetManyByOrganizationIdAsync(organizationId, userId, AccessClientType.NoAccessCheck);
var secrets = await _secretRepository.GetManyByOrganizationIdAsync(organizationId, userId, AccessClientType.NoAccessCheck);
var secrets = await _secretRepository.GetManyDetailsByOrganizationIdAsync(organizationId, userId, AccessClientType.NoAccessCheck);
if (projects == null && secrets == null)
{

View File

@ -41,7 +41,7 @@ public class TrashController : Controller
throw new UnauthorizedAccessException();
}
var secrets = await _secretRepository.GetManyByOrganizationIdInTrashAsync(organizationId);
var secrets = await _secretRepository.GetManyDetailsByOrganizationIdInTrashAsync(organizationId);
return new SecretWithProjectsListResponseModel(secrets);
}

View File

@ -0,0 +1,27 @@
#nullable enable
using Bit.Api.Models.Response;
using Bit.Core.Models.Api;
using Bit.Core.SecretsManager.Entities;
namespace Bit.Api.SecretsManager.Models.Response;
public class SecretsSyncResponseModel : ResponseModel
{
private const string _objectName = "secretsSync";
public bool HasChanges { get; set; }
public ListResponseModel<BaseSecretResponseModel>? Secrets { get; set; }
public SecretsSyncResponseModel(bool hasChanges, IEnumerable<Secret>? secrets, string obj = _objectName)
: base(obj)
{
Secrets = secrets != null
? new ListResponseModel<BaseSecretResponseModel>(secrets.Select(s => new BaseSecretResponseModel(s)))
: null;
HasChanges = hasChanges;
}
public SecretsSyncResponseModel() : base(_objectName)
{
}
}