1
0
mirror of https://github.com/bitwarden/server synced 2026-01-15 06:53:26 +00:00
Files
server/src/Api/Dirt/Controllers/OrganizationReportsController.cs
Graham Walker 226f274a72 Organization report tables, repos, services, and endpoints (#6158)
* PM-23754 initial commit

* pm-23754 fixing controller tests

* pm-23754 adding commands and queries

* pm-23754 adding endpoints, command/queries, repositories, and sql migrations

* pm-23754 add new sql scripts

* PM-23754 adding sql scripts

* pm-23754

* PM-23754 fixing migration script

* PM-23754 fixing migration script again

* PM-23754 fixing migration script validation

* PM-23754 fixing db validation script issue

* PM-23754 fixing endpoint and db validation

* PM-23754 fixing unit tests

* PM-23754 fixing implementation based on comments and tests

* PM-23754 updating logging statements

* PM-23754 making changes based on PR comments.

* updating migration scripts

* removing old migration files

* update code based testing for whole data object for OrganizationReport and add a stored procedure.

* updating services, unit tests, repository tests

* fixing unit tests

* fixing migration script

* fixing migration script again

* fixing migration script

* another fix

* fixing sql file, updating controller to account for different orgIds in the url and body.

* updating error message in controllers without a body

* making a change to the command

* Refactor ReportsController by removing organization reports

The IDropOrganizationReportCommand is no longer needed

* will code based on PR comments.

* fixing unit test

* fixing migration script based on last changes.

* adding another check in endpoint and adding unit tests

* fixing route parameter.

* PM-23754 updating data fields to return just the column

* PM-23754 fixing repository method signatures

* PM-23754 making change to orgId parameter through out code to align with api naming

---------

Co-authored-by: Tom <144813356+ttalty@users.noreply.github.com>
2025-09-08 15:06:13 -05:00

298 lines
11 KiB
C#

using Bit.Core.Context;
using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
using Bit.Core.Exceptions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Dirt.Controllers;
[Route("reports/organizations")]
[Authorize("Application")]
public class OrganizationReportsController : Controller
{
private readonly ICurrentContext _currentContext;
private readonly IGetOrganizationReportQuery _getOrganizationReportQuery;
private readonly IAddOrganizationReportCommand _addOrganizationReportCommand;
private readonly IUpdateOrganizationReportCommand _updateOrganizationReportCommand;
private readonly IUpdateOrganizationReportSummaryCommand _updateOrganizationReportSummaryCommand;
private readonly IGetOrganizationReportSummaryDataQuery _getOrganizationReportSummaryDataQuery;
private readonly IGetOrganizationReportSummaryDataByDateRangeQuery _getOrganizationReportSummaryDataByDateRangeQuery;
private readonly IGetOrganizationReportDataQuery _getOrganizationReportDataQuery;
private readonly IUpdateOrganizationReportDataCommand _updateOrganizationReportDataCommand;
private readonly IGetOrganizationReportApplicationDataQuery _getOrganizationReportApplicationDataQuery;
private readonly IUpdateOrganizationReportApplicationDataCommand _updateOrganizationReportApplicationDataCommand;
public OrganizationReportsController(
ICurrentContext currentContext,
IGetOrganizationReportQuery getOrganizationReportQuery,
IAddOrganizationReportCommand addOrganizationReportCommand,
IUpdateOrganizationReportCommand updateOrganizationReportCommand,
IUpdateOrganizationReportSummaryCommand updateOrganizationReportSummaryCommand,
IGetOrganizationReportSummaryDataQuery getOrganizationReportSummaryDataQuery,
IGetOrganizationReportSummaryDataByDateRangeQuery getOrganizationReportSummaryDataByDateRangeQuery,
IGetOrganizationReportDataQuery getOrganizationReportDataQuery,
IUpdateOrganizationReportDataCommand updateOrganizationReportDataCommand,
IGetOrganizationReportApplicationDataQuery getOrganizationReportApplicationDataQuery,
IUpdateOrganizationReportApplicationDataCommand updateOrganizationReportApplicationDataCommand
)
{
_currentContext = currentContext;
_getOrganizationReportQuery = getOrganizationReportQuery;
_addOrganizationReportCommand = addOrganizationReportCommand;
_updateOrganizationReportCommand = updateOrganizationReportCommand;
_updateOrganizationReportSummaryCommand = updateOrganizationReportSummaryCommand;
_getOrganizationReportSummaryDataQuery = getOrganizationReportSummaryDataQuery;
_getOrganizationReportSummaryDataByDateRangeQuery = getOrganizationReportSummaryDataByDateRangeQuery;
_getOrganizationReportDataQuery = getOrganizationReportDataQuery;
_updateOrganizationReportDataCommand = updateOrganizationReportDataCommand;
_getOrganizationReportApplicationDataQuery = getOrganizationReportApplicationDataQuery;
_updateOrganizationReportApplicationDataCommand = updateOrganizationReportApplicationDataCommand;
}
#region Whole OrganizationReport Endpoints
[HttpGet("{organizationId}/latest")]
public async Task<IActionResult> GetLatestOrganizationReportAsync(Guid organizationId)
{
if (!await _currentContext.AccessReports(organizationId))
{
throw new NotFoundException();
}
var latestReport = await _getOrganizationReportQuery.GetLatestOrganizationReportAsync(organizationId);
return Ok(latestReport);
}
[HttpGet("{organizationId}/{reportId}")]
public async Task<IActionResult> GetOrganizationReportAsync(Guid organizationId, Guid reportId)
{
if (!await _currentContext.AccessReports(organizationId))
{
throw new NotFoundException();
}
var report = await _getOrganizationReportQuery.GetOrganizationReportAsync(reportId);
if (report == null)
{
throw new NotFoundException("Report not found for the specified organization.");
}
if (report.OrganizationId != organizationId)
{
throw new BadRequestException("Invalid report ID");
}
return Ok(report);
}
[HttpPost("{organizationId}")]
public async Task<IActionResult> CreateOrganizationReportAsync(Guid organizationId, [FromBody] AddOrganizationReportRequest request)
{
if (!await _currentContext.AccessReports(organizationId))
{
throw new NotFoundException();
}
if (request.OrganizationId != organizationId)
{
throw new BadRequestException("Organization ID in the request body must match the route parameter");
}
var report = await _addOrganizationReportCommand.AddOrganizationReportAsync(request);
return Ok(report);
}
[HttpPatch("{organizationId}/{reportId}")]
public async Task<IActionResult> UpdateOrganizationReportAsync(Guid organizationId, [FromBody] UpdateOrganizationReportRequest request)
{
if (!await _currentContext.AccessReports(organizationId))
{
throw new NotFoundException();
}
if (request.OrganizationId != organizationId)
{
throw new BadRequestException("Organization ID in the request body must match the route parameter");
}
var updatedReport = await _updateOrganizationReportCommand.UpdateOrganizationReportAsync(request);
return Ok(updatedReport);
}
#endregion
# region SummaryData Field Endpoints
[HttpGet("{organizationId}/data/summary")]
public async Task<IActionResult> GetOrganizationReportSummaryDataByDateRangeAsync(
Guid organizationId, [FromQuery] DateTime startDate, [FromQuery] DateTime endDate)
{
if (!await _currentContext.AccessReports(organizationId))
{
throw new NotFoundException();
}
if (organizationId.Equals(null))
{
throw new BadRequestException("Organization ID is required.");
}
var summaryDataList = await _getOrganizationReportSummaryDataByDateRangeQuery
.GetOrganizationReportSummaryDataByDateRangeAsync(organizationId, startDate, endDate);
return Ok(summaryDataList);
}
[HttpGet("{organizationId}/data/summary/{reportId}")]
public async Task<IActionResult> GetOrganizationReportSummaryAsync(Guid organizationId, Guid reportId)
{
if (!await _currentContext.AccessReports(organizationId))
{
throw new NotFoundException();
}
var summaryData =
await _getOrganizationReportSummaryDataQuery.GetOrganizationReportSummaryDataAsync(organizationId, reportId);
if (summaryData == null)
{
throw new NotFoundException("Report not found for the specified organization.");
}
return Ok(summaryData);
}
[HttpPatch("{organizationId}/data/summary/{reportId}")]
public async Task<IActionResult> UpdateOrganizationReportSummaryAsync(Guid organizationId, Guid reportId, [FromBody] UpdateOrganizationReportSummaryRequest request)
{
if (!await _currentContext.AccessReports(organizationId))
{
throw new NotFoundException();
}
if (request.OrganizationId != organizationId)
{
throw new BadRequestException("Organization ID in the request body must match the route parameter");
}
if (request.ReportId != reportId)
{
throw new BadRequestException("Report ID in the request body must match the route parameter");
}
var updatedReport = await _updateOrganizationReportSummaryCommand.UpdateOrganizationReportSummaryAsync(request);
return Ok(updatedReport);
}
#endregion
#region ReportData Field Endpoints
[HttpGet("{organizationId}/data/report/{reportId}")]
public async Task<IActionResult> GetOrganizationReportDataAsync(Guid organizationId, Guid reportId)
{
if (!await _currentContext.AccessReports(organizationId))
{
throw new NotFoundException();
}
var reportData = await _getOrganizationReportDataQuery.GetOrganizationReportDataAsync(organizationId, reportId);
if (reportData == null)
{
throw new NotFoundException("Organization report data not found.");
}
return Ok(reportData);
}
[HttpPatch("{organizationId}/data/report/{reportId}")]
public async Task<IActionResult> UpdateOrganizationReportDataAsync(Guid organizationId, Guid reportId, [FromBody] UpdateOrganizationReportDataRequest request)
{
if (!await _currentContext.AccessReports(organizationId))
{
throw new NotFoundException();
}
if (request.OrganizationId != organizationId)
{
throw new BadRequestException("Organization ID in the request body must match the route parameter");
}
if (request.ReportId != reportId)
{
throw new BadRequestException("Report ID in the request body must match the route parameter");
}
var updatedReport = await _updateOrganizationReportDataCommand.UpdateOrganizationReportDataAsync(request);
return Ok(updatedReport);
}
#endregion
#region ApplicationData Field Endpoints
[HttpGet("{organizationId}/data/application/{reportId}")]
public async Task<IActionResult> GetOrganizationReportApplicationDataAsync(Guid organizationId, Guid reportId)
{
try
{
if (!await _currentContext.AccessReports(organizationId))
{
throw new NotFoundException();
}
var applicationData = await _getOrganizationReportApplicationDataQuery.GetOrganizationReportApplicationDataAsync(organizationId, reportId);
if (applicationData == null)
{
throw new NotFoundException("Organization report application data not found.");
}
return Ok(applicationData);
}
catch (Exception ex) when (!(ex is BadRequestException || ex is NotFoundException))
{
throw;
}
}
[HttpPatch("{organizationId}/data/application/{reportId}")]
public async Task<IActionResult> UpdateOrganizationReportApplicationDataAsync(Guid organizationId, Guid reportId, [FromBody] UpdateOrganizationReportApplicationDataRequest request)
{
try
{
if (!await _currentContext.AccessReports(organizationId))
{
throw new NotFoundException();
}
if (request.OrganizationId != organizationId)
{
throw new BadRequestException("Organization ID in the request body must match the route parameter");
}
if (request.Id != reportId)
{
throw new BadRequestException("Report ID in the request body must match the route parameter");
}
var updatedReport = await _updateOrganizationReportApplicationDataCommand.UpdateOrganizationReportApplicationDataAsync(request);
return Ok(updatedReport);
}
catch (Exception ex) when (!(ex is BadRequestException || ex is NotFoundException))
{
throw;
}
}
#endregion
}