mirror of
https://github.com/bitwarden/server
synced 2026-02-21 03:43:44 +00: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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
12
src/Core/SecretsManager/Models/Data/SecretsSyncRequest.cs
Normal file
12
src/Core/SecretsManager/Models/Data/SecretsSyncRequest.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
public class SecretsSyncRequest
|
||||
{
|
||||
public AccessClientType AccessClientType { get; set; }
|
||||
public Guid OrganizationId { get; set; }
|
||||
public Guid ServiceAccountId { get; set; }
|
||||
public DateTime? LastSyncedDate { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#nullable enable
|
||||
using Bit.Core.SecretsManager.Entities;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
namespace Bit.Core.SecretsManager.Queries.Secrets.Interfaces;
|
||||
|
||||
public interface ISecretsSyncQuery
|
||||
{
|
||||
Task<(bool HasChanges, IEnumerable<Secret>? Secrets)> GetAsync(SecretsSyncRequest syncRequest);
|
||||
}
|
||||
@@ -6,11 +6,12 @@ namespace Bit.Core.SecretsManager.Repositories;
|
||||
|
||||
public interface ISecretRepository
|
||||
{
|
||||
Task<IEnumerable<SecretPermissionDetails>> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType);
|
||||
Task<IEnumerable<SecretPermissionDetails>> GetManyByOrganizationIdInTrashAsync(Guid organizationId);
|
||||
Task<IEnumerable<SecretPermissionDetails>> GetManyDetailsByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType);
|
||||
Task<IEnumerable<SecretPermissionDetails>> GetManyDetailsByOrganizationIdInTrashAsync(Guid organizationId);
|
||||
Task<IEnumerable<SecretPermissionDetails>> GetManyDetailsByProjectIdAsync(Guid projectId, Guid userId, AccessClientType accessType);
|
||||
Task<IEnumerable<Secret>> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId, AccessClientType accessType);
|
||||
Task<IEnumerable<Secret>> GetManyByOrganizationIdInTrashByIdsAsync(Guid organizationId, IEnumerable<Guid> ids);
|
||||
Task<IEnumerable<Secret>> GetManyByIds(IEnumerable<Guid> ids);
|
||||
Task<IEnumerable<SecretPermissionDetails>> GetManyByProjectIdAsync(Guid projectId, Guid userId, AccessClientType accessType);
|
||||
Task<Secret> GetByIdAsync(Guid id);
|
||||
Task<Secret> CreateAsync(Secret secret);
|
||||
Task<Secret> UpdateAsync(Secret secret);
|
||||
@@ -18,7 +19,6 @@ public interface ISecretRepository
|
||||
Task HardDeleteManyByIdAsync(IEnumerable<Guid> ids);
|
||||
Task RestoreManyByIdAsync(IEnumerable<Guid> ids);
|
||||
Task<IEnumerable<Secret>> ImportAsync(IEnumerable<Secret> secrets);
|
||||
Task UpdateRevisionDates(IEnumerable<Guid> ids);
|
||||
Task<(bool Read, bool Write)> AccessToSecretAsync(Guid id, Guid userId, AccessClientType accessType);
|
||||
Task EmptyTrash(DateTime nowTime, uint deleteAfterThisNumberOfDays);
|
||||
Task<int> GetSecretsCountByOrganizationIdAsync(Guid organizationId);
|
||||
|
||||
@@ -6,17 +6,23 @@ namespace Bit.Core.SecretsManager.Repositories.Noop;
|
||||
|
||||
public class NoopSecretRepository : ISecretRepository
|
||||
{
|
||||
public Task<IEnumerable<SecretPermissionDetails>> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId,
|
||||
public Task<IEnumerable<SecretPermissionDetails>> GetManyDetailsByOrganizationIdAsync(Guid organizationId, Guid userId,
|
||||
AccessClientType accessType)
|
||||
{
|
||||
return Task.FromResult(null as IEnumerable<SecretPermissionDetails>);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<SecretPermissionDetails>> GetManyByOrganizationIdInTrashAsync(Guid organizationId)
|
||||
public Task<IEnumerable<SecretPermissionDetails>> GetManyDetailsByOrganizationIdInTrashAsync(Guid organizationId)
|
||||
{
|
||||
return Task.FromResult(null as IEnumerable<SecretPermissionDetails>);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<Secret>> GetManyByOrganizationIdAsync(Guid organizationId, Guid userId,
|
||||
AccessClientType accessType)
|
||||
{
|
||||
return Task.FromResult(null as IEnumerable<Secret>);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<Secret>> GetManyByOrganizationIdInTrashByIdsAsync(Guid organizationId,
|
||||
IEnumerable<Guid> ids)
|
||||
{
|
||||
@@ -28,7 +34,7 @@ public class NoopSecretRepository : ISecretRepository
|
||||
return Task.FromResult(null as IEnumerable<Secret>);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<SecretPermissionDetails>> GetManyByProjectIdAsync(Guid projectId, Guid userId,
|
||||
public Task<IEnumerable<SecretPermissionDetails>> GetManyDetailsByProjectIdAsync(Guid projectId, Guid userId,
|
||||
AccessClientType accessType)
|
||||
{
|
||||
return Task.FromResult(null as IEnumerable<SecretPermissionDetails>);
|
||||
@@ -69,11 +75,6 @@ public class NoopSecretRepository : ISecretRepository
|
||||
return Task.FromResult(null as IEnumerable<Secret>);
|
||||
}
|
||||
|
||||
public Task UpdateRevisionDates(IEnumerable<Guid> ids)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task<(bool Read, bool Write)> AccessToSecretAsync(Guid id, Guid userId, AccessClientType accessType)
|
||||
{
|
||||
return Task.FromResult((false, false));
|
||||
|
||||
Reference in New Issue
Block a user