1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 23:52:50 -05:00

[SM-863] Add endpoint for fetching multiple secrets by IDs (#3134)

* Add support CanReadSecret authorization

* Extract base response model for secret

* Add support for SA bulk fetching event logging

* secret repository bug fix

* Add endpoint and request for bulk fetching secrets

* Swap to original reference event

* Add unit tests

* Add integration tests

* Add unit tests for authz handler

* update authz handler tests

---------
This commit is contained in:
Thomas Avery
2023-08-28 10:16:50 -05:00
committed by GitHub
parent 8eee9b330d
commit 640cb68d51
13 changed files with 394 additions and 62 deletions

View File

@ -207,4 +207,45 @@ public class SecretsController : Controller
var responses = results.Select(r => new BulkDeleteResponseModel(r.Secret.Id, r.Error));
return new ListResponseModel<BulkDeleteResponseModel>(responses);
}
[HttpPost("secrets/get-by-ids")]
public async Task<ListResponseModel<BaseSecretResponseModel>> GetSecretsByIdsAsync(
[FromBody] GetSecretsRequestModel request)
{
var secrets = (await _secretRepository.GetManyByIds(request.Ids)).ToList();
if (!secrets.Any() || secrets.Count != request.Ids.Count())
{
throw new NotFoundException();
}
// Ensure all secrets belong to the same organization.
var organizationId = secrets.First().OrganizationId;
if (secrets.Any(secret => secret.OrganizationId != organizationId) ||
!_currentContext.AccessSecretsManager(organizationId))
{
throw new NotFoundException();
}
foreach (var secret in secrets)
{
var authorizationResult = await _authorizationService.AuthorizeAsync(User, secret, SecretOperations.Read);
if (!authorizationResult.Succeeded)
{
throw new NotFoundException();
}
}
if (_currentContext.ClientType == ClientType.ServiceAccount)
{
var userId = _userService.GetProperUserId(User).Value;
var org = await _organizationRepository.GetByIdAsync(organizationId);
await _eventService.LogServiceAccountSecretsEventAsync(userId, secrets, EventType.Secret_Retrieved);
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.SmServiceAccountAccessedSecret, org, _currentContext));
}
var responses = secrets.Select(s => new BaseSecretResponseModel(s));
return new ListResponseModel<BaseSecretResponseModel>(responses);
}
}

View File

@ -0,0 +1,9 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Api.SecretsManager.Models.Request;
public class GetSecretsRequestModel
{
[Required]
public IEnumerable<Guid> Ids { get; set; }
}

View File

@ -0,0 +1,66 @@
using Bit.Core.Models.Api;
using Bit.Core.SecretsManager.Entities;
namespace Bit.Api.SecretsManager.Models.Response;
public class BaseSecretResponseModel : ResponseModel
{
private const string _objectName = "baseSecret";
public BaseSecretResponseModel(Secret secret, string objectName = _objectName) : base(objectName)
{
if (secret == null)
{
throw new ArgumentNullException(nameof(secret));
}
Id = secret.Id;
OrganizationId = secret.OrganizationId;
Key = secret.Key;
Value = secret.Value;
Note = secret.Note;
CreationDate = secret.CreationDate;
RevisionDate = secret.RevisionDate;
Projects = secret.Projects?.Select(p => new SecretResponseInnerProject(p));
}
public BaseSecretResponseModel(string objectName = _objectName) : base(objectName)
{
}
public BaseSecretResponseModel() : base(_objectName)
{
}
public Guid Id { get; set; }
public Guid OrganizationId { get; set; }
public string Key { get; set; }
public string Value { get; set; }
public string Note { get; set; }
public DateTime CreationDate { get; set; }
public DateTime RevisionDate { get; set; }
public IEnumerable<SecretResponseInnerProject> Projects { get; set; }
public class SecretResponseInnerProject
{
public SecretResponseInnerProject(Project project)
{
Id = project.Id;
Name = project.Name;
}
public SecretResponseInnerProject()
{
}
public Guid Id { get; set; }
public string Name { get; set; }
}
}

View File

@ -1,28 +1,13 @@
using Bit.Core.Models.Api;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Entities;
namespace Bit.Api.SecretsManager.Models.Response;
public class SecretResponseModel : ResponseModel
public class SecretResponseModel : BaseSecretResponseModel
{
private const string _objectName = "secret";
public SecretResponseModel(Secret secret, bool read, bool write) : base(_objectName)
public SecretResponseModel(Secret secret, bool read, bool write) : base(secret, _objectName)
{
if (secret == null)
{
throw new ArgumentNullException(nameof(secret));
}
Id = secret.Id;
OrganizationId = secret.OrganizationId;
Key = secret.Key;
Value = secret.Value;
Note = secret.Note;
CreationDate = secret.CreationDate;
RevisionDate = secret.RevisionDate;
Projects = secret.Projects?.Select(p => new SecretResponseInnerProject(p));
Read = read;
Write = write;
}
@ -31,39 +16,7 @@ public class SecretResponseModel : ResponseModel
{
}
public Guid Id { get; set; }
public Guid OrganizationId { get; set; }
public string Key { get; set; }
public string Value { get; set; }
public string Note { get; set; }
public DateTime CreationDate { get; set; }
public DateTime RevisionDate { get; set; }
public IEnumerable<SecretResponseInnerProject> Projects { get; set; }
public bool Read { get; set; }
public bool Write { get; set; }
public class SecretResponseInnerProject
{
public SecretResponseInnerProject(Project project)
{
Id = project.Id;
Name = project.Name;
}
public SecretResponseInnerProject()
{
}
public Guid Id { get; set; }
public string Name { get; set; }
}
}