Skip to content

Commit cf80639

Browse files
committed
Justin Baurs suggested changes
1 parent e48b8c6 commit cf80639

File tree

4 files changed

+91
-8
lines changed

4 files changed

+91
-8
lines changed

bitwarden_license/src/Commercial.Infrastructure.EntityFramework/SecretsManager/Repositories/SecretVersionRepository.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ public SecretVersionRepository(IServiceScopeFactory serviceScopeFactory, IMapper
3434
return Mapper.Map<List<Core.SecretsManager.Entities.SecretVersion>>(secretVersions);
3535
}
3636

37+
public async Task<IEnumerable<Core.SecretsManager.Entities.SecretVersion>> GetManyByIdsAsync(IEnumerable<Guid> ids)
38+
{
39+
using var scope = ServiceScopeFactory.CreateScope();
40+
var dbContext = GetDatabaseContext(scope);
41+
var versionIds = ids.ToList();
42+
var secretVersions = await dbContext.SecretVersion
43+
.Where(sv => versionIds.Contains(sv.Id))
44+
.OrderByDescending(sv => sv.VersionDate)
45+
.ToListAsync();
46+
return Mapper.Map<List<Core.SecretsManager.Entities.SecretVersion>>(secretVersions);
47+
}
48+
3749
public override async Task<Core.SecretsManager.Entities.SecretVersion> CreateAsync(Core.SecretsManager.Entities.SecretVersion secretVersion)
3850
{
3951
const int maxVersionsToKeep = 10;

src/Api/SecretsManager/Controllers/SecretVersionsController.cs

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,67 @@ public async Task<SecretVersionResponseModel> GetByIdAsync([FromRoute] Guid id)
113113
return new SecretVersionResponseModel(secretVersion);
114114
}
115115

116+
[HttpPost("secret-versions/get-by-ids")]
117+
public async Task<ListResponseModel<SecretVersionResponseModel>> GetManyByIdsAsync([FromBody] List<Guid> ids)
118+
{
119+
if (!ids.Any())
120+
{
121+
throw new BadRequestException("No version IDs provided.");
122+
}
123+
124+
// Get all versions
125+
var versions = (await _secretVersionRepository.GetManyByIdsAsync(ids)).ToList();
126+
if (!versions.Any())
127+
{
128+
throw new NotFoundException();
129+
}
130+
131+
// Get all associated secrets and check permissions
132+
var secretIds = versions.Select(v => v.SecretId).Distinct().ToList();
133+
var secrets = (await _secretRepository.GetManyByIds(secretIds)).ToList();
134+
135+
if (!secrets.Any())
136+
{
137+
throw new NotFoundException();
138+
}
139+
140+
// Ensure all secrets belong to the same organization
141+
var organizationId = secrets.First().OrganizationId;
142+
if (secrets.Any(s => s.OrganizationId != organizationId) ||
143+
!_currentContext.AccessSecretsManager(organizationId))
144+
{
145+
throw new NotFoundException();
146+
}
147+
148+
// For service accounts and organization API, skip user-level access checks
149+
if (_currentContext.IdentityClientType == IdentityClientType.ServiceAccount ||
150+
_currentContext.IdentityClientType == IdentityClientType.Organization)
151+
{
152+
// Already verified Secrets Manager access and organization ownership above
153+
var serviceAccountResponses = versions.Select(v => new SecretVersionResponseModel(v));
154+
return new ListResponseModel<SecretVersionResponseModel>(serviceAccountResponses);
155+
}
156+
157+
var userId = _userService.GetProperUserId(User);
158+
if (!userId.HasValue)
159+
{
160+
throw new NotFoundException();
161+
}
162+
163+
var isAdmin = await _currentContext.OrganizationAdmin(organizationId);
164+
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.IdentityClientType, isAdmin);
165+
166+
// Verify read access to all associated secrets
167+
var accessResults = await _secretRepository.AccessToSecretsAsync(secretIds, userId.Value, accessClient);
168+
if (accessResults.Values.Any(access => !access.Read))
169+
{
170+
throw new NotFoundException();
171+
}
172+
173+
var responses = versions.Select(v => new SecretVersionResponseModel(v));
174+
return new ListResponseModel<SecretVersionResponseModel>(responses);
175+
}
176+
116177
[HttpPut("secrets/{secretId}/versions/restore")]
117178
public async Task<SecretResponseModel> RestoreVersionAsync([FromRoute] Guid secretId, [FromBody] RestoreSecretVersionRequestModel request)
118179
{
@@ -228,13 +289,10 @@ public async Task<IActionResult> BulkDeleteAsync([FromBody] List<Guid> ids)
228289
var accessClient = AccessClientHelper.ToAccessClient(_currentContext.IdentityClientType, orgAdmin);
229290

230291
// Verify write access to all associated secrets
231-
foreach (var secretId in secretIds)
292+
var accessResults = await _secretRepository.AccessToSecretsAsync(secretIds, userId.Value, accessClient);
293+
if (accessResults.Values.Any(access => !access.Write))
232294
{
233-
var access = await _secretRepository.AccessToSecretAsync(secretId, userId.Value, accessClient);
234-
if (!access.Write)
235-
{
236-
throw new NotFoundException();
237-
}
295+
throw new NotFoundException();
238296
}
239297

240298
await _secretVersionRepository.DeleteManyByIdAsync(ids);

src/Api/SecretsManager/Controllers/SecretsController.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Bit.Core.Context;
99
using Bit.Core.Enums;
1010
using Bit.Core.Exceptions;
11+
using Bit.Core.Repositories;
1112
using Bit.Core.SecretsManager.AuthorizationRequirements;
1213
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
1314
using Bit.Core.SecretsManager.Entities;
@@ -39,6 +40,7 @@ public class SecretsController : Controller
3940
private readonly IUserService _userService;
4041
private readonly IEventService _eventService;
4142
private readonly IAuthorizationService _authorizationService;
43+
private readonly IOrganizationUserRepository _organizationUserRepository;
4244

4345
public SecretsController(
4446
ICurrentContext currentContext,
@@ -53,7 +55,8 @@ public SecretsController(
5355
ISecretAccessPoliciesUpdatesQuery secretAccessPoliciesUpdatesQuery,
5456
IUserService userService,
5557
IEventService eventService,
56-
IAuthorizationService authorizationService)
58+
IAuthorizationService authorizationService,
59+
IOrganizationUserRepository organizationUserRepository)
5760
{
5861
_currentContext = currentContext;
5962
_projectRepository = projectRepository;
@@ -68,6 +71,7 @@ public SecretsController(
6871
_userService = userService;
6972
_eventService = eventService;
7073
_authorizationService = authorizationService;
74+
_organizationUserRepository = organizationUserRepository;
7175

7276
}
7377

@@ -209,7 +213,15 @@ public async Task<SecretResponseModel> UpdateSecretAsync([FromRoute] Guid id, [F
209213
}
210214
else if (_currentContext.IdentityClientType == IdentityClientType.User)
211215
{
212-
editorOrganizationUserId = userId;
216+
var orgUser = await _organizationUserRepository.GetByOrganizationAsync(secret.OrganizationId, userId);
217+
if (orgUser != null)
218+
{
219+
editorOrganizationUserId = orgUser.Id;
220+
}
221+
else
222+
{
223+
throw new NotFoundException();
224+
}
213225
}
214226

215227
var secretVersion = new SecretVersion

src/Core/SecretsManager/Repositories/ISecretVersionRepository.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ public interface ISecretVersionRepository
66
{
77
Task<SecretVersion?> GetByIdAsync(Guid id);
88
Task<IEnumerable<SecretVersion>> GetManyBySecretIdAsync(Guid secretId);
9+
Task<IEnumerable<SecretVersion>> GetManyByIdsAsync(IEnumerable<Guid> ids);
910
Task<SecretVersion> CreateAsync(SecretVersion secretVersion);
1011
Task DeleteManyByIdAsync(IEnumerable<Guid> ids);
1112
}

0 commit comments

Comments
 (0)