1
0
mirror of https://github.com/bitwarden/server synced 2026-01-05 01:53:17 +00:00

[SM-1274] Adding Project Events (#6022)

* Adding new logging for secrets

* fixing secrest controller tests

* fixing the tests

* Server side changes for adding ProjectId to Event table, adding Project event logging to projectsController

* Rough draft with TODO's need to work on EventRepository.cs, and ProjectRepository.cs

* Undoing changes to make projects soft delete, we want those to be fully deleted still. Adding GetManyTrashedSecretsByIds to secret repo so we can get soft deleted secrets, getSecrets in eventsController takes in orgdId, so that we can check the permission even if the secret was permanently deleted and doesn' thave the org Id set. Adding Secret Perm Deleted, and Restored to event logs

* db changes

* fixing the way we log events

* Trying to undo some manual changes that should have been migrations

* adding migration files

* fixing test

* setting up userid for project controller tests

* adding sql

* sql

* Rename file

* Trying to get it to for sure add the column before we try and update sprocs

* Adding code to refresh the view to include ProjectId I hope

* code improvements

* Suggested changes

* suggested changes

* trying to fix sql issues

* fixing swagger issue

* Update src/Core/SecretsManager/Repositories/Noop/NoopSecretRepository.cs

Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>

* Suggested changes

---------

Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>
This commit is contained in:
cd-bitwarden
2025-08-20 10:24:17 -04:00
committed by GitHub
parent 7a6fa5a457
commit 3cad054af1
39 changed files with 10698 additions and 15 deletions

View File

@@ -7,6 +7,7 @@ using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Identity;
using Bit.Core.SecretsManager.AuthorizationRequirements;
using Bit.Core.SecretsManager.Commands.Projects.Interfaces;
using Bit.Core.SecretsManager.Entities;
@@ -29,6 +30,7 @@ public class ProjectsController : Controller
private readonly IUpdateProjectCommand _updateProjectCommand;
private readonly IDeleteProjectCommand _deleteProjectCommand;
private readonly IAuthorizationService _authorizationService;
private readonly IEventService _eventService;
public ProjectsController(
ICurrentContext currentContext,
@@ -38,7 +40,8 @@ public class ProjectsController : Controller
ICreateProjectCommand createProjectCommand,
IUpdateProjectCommand updateProjectCommand,
IDeleteProjectCommand deleteProjectCommand,
IAuthorizationService authorizationService)
IAuthorizationService authorizationService,
IEventService eventService)
{
_currentContext = currentContext;
_userService = userService;
@@ -48,6 +51,7 @@ public class ProjectsController : Controller
_updateProjectCommand = updateProjectCommand;
_deleteProjectCommand = deleteProjectCommand;
_authorizationService = authorizationService;
_eventService = eventService;
}
[HttpGet("organizations/{organizationId}/projects")]
@@ -89,6 +93,11 @@ public class ProjectsController : Controller
var userId = _userService.GetProperUserId(User).Value;
var result = await _createProjectCommand.CreateAsync(project, userId, _currentContext.IdentityClientType);
if (result != null)
{
await LogProjectEventAsync(project, EventType.Project_Created);
}
// Creating a project means you have read & write permission.
return new ProjectResponseModel(result, true, true);
}
@@ -106,6 +115,10 @@ public class ProjectsController : Controller
}
var result = await _updateProjectCommand.UpdateAsync(updateRequest.ToProject(id));
if (result != null)
{
await LogProjectEventAsync(project, EventType.Project_Edited);
}
// Updating a project means you have read & write permission.
return new ProjectResponseModel(result, true, true);
@@ -136,6 +149,8 @@ public class ProjectsController : Controller
throw new NotFoundException();
}
await LogProjectEventAsync(project, EventType.Project_Retrieved);
return new ProjectResponseModel(project, access.Read, access.Write);
}
@@ -175,9 +190,32 @@ public class ProjectsController : Controller
}
}
await _deleteProjectCommand.DeleteProjects(projectsToDelete);
if (projectsToDelete.Count > 0)
{
await _deleteProjectCommand.DeleteProjects(projectsToDelete);
await LogProjectsEventAsync(projectsToDelete, EventType.Project_Deleted);
}
var responses = results.Select(r => new BulkDeleteResponseModel(r.Project.Id, r.Error));
return new ListResponseModel<BulkDeleteResponseModel>(responses);
}
private async Task LogProjectsEventAsync(IEnumerable<Project> projects, EventType eventType)
{
var userId = _userService.GetProperUserId(User)!.Value;
switch (_currentContext.IdentityClientType)
{
case IdentityClientType.ServiceAccount:
await _eventService.LogServiceAccountProjectsEventAsync(userId, projects, eventType);
break;
case IdentityClientType.User:
await _eventService.LogUserProjectsEventAsync(userId, projects, eventType);
break;
}
}
private Task LogProjectEventAsync(Project project, EventType eventType) =>
LogProjectsEventAsync(new[] { project }, eventType);
}

View File

@@ -1,8 +1,12 @@
using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Identity;
using Bit.Core.SecretsManager.Commands.Trash.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -15,17 +19,23 @@ public class TrashController : Controller
private readonly ISecretRepository _secretRepository;
private readonly IEmptyTrashCommand _emptyTrashCommand;
private readonly IRestoreTrashCommand _restoreTrashCommand;
private readonly IUserService _userService;
private readonly IEventService _eventService;
public TrashController(
ICurrentContext currentContext,
ISecretRepository secretRepository,
IEmptyTrashCommand emptyTrashCommand,
IRestoreTrashCommand restoreTrashCommand)
IRestoreTrashCommand restoreTrashCommand,
IUserService userService,
IEventService eventService)
{
_currentContext = currentContext;
_secretRepository = secretRepository;
_emptyTrashCommand = emptyTrashCommand;
_restoreTrashCommand = restoreTrashCommand;
_userService = userService;
_eventService = eventService;
}
[HttpGet("secrets/{organizationId}/trash")]
@@ -58,7 +68,9 @@ public class TrashController : Controller
throw new UnauthorizedAccessException();
}
var deletedSecrets = await _secretRepository.GetManyTrashedSecretsByIds(ids);
await _emptyTrashCommand.EmptyTrash(organizationId, ids);
await LogSecretsTrashEventAsync(deletedSecrets, EventType.Secret_Permanently_Deleted);
}
[HttpPost("secrets/{organizationId}/trash/restore")]
@@ -75,5 +87,27 @@ public class TrashController : Controller
}
await _restoreTrashCommand.RestoreTrash(organizationId, ids);
await LogSecretsTrashEventAsync(ids, EventType.Secret_Restored);
}
private async Task LogSecretsTrashEventAsync(IEnumerable<Guid> secretIds, EventType eventType)
{
var secrets = await _secretRepository.GetManyByIds(secretIds);
await LogSecretsTrashEventAsync(secrets, eventType);
}
private async Task LogSecretsTrashEventAsync(IEnumerable<Secret> secrets, EventType eventType)
{
var userId = _userService.GetProperUserId(User)!.Value;
switch (_currentContext.IdentityClientType)
{
case IdentityClientType.ServiceAccount:
await _eventService.LogServiceAccountSecretsEventAsync(userId, secrets, eventType);
break;
case IdentityClientType.User:
await _eventService.LogUserSecretsEventAsync(userId, secrets, eventType);
break;
}
}
}