Skip to content
73 changes: 36 additions & 37 deletions src/Api/Dirt/Controllers/OrganizationReportsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -467,43 +467,6 @@ public async Task<IActionResult> DownloadReportFileAsync(Guid organizationId, Gu
return File(stream, "application/octet-stream", fileData.FileName);
}

private async Task AuthorizeAsync(Guid organizationId)
{
if (!await _currentContext.AccessReports(organizationId))
{
throw new NotFoundException();
}

var orgAbility = await _organizationAbilityCacheService.GetOrganizationAbilityAsync(organizationId);
if (orgAbility is null || !orgAbility.UseRiskInsights)
{
throw new BadRequestException("Your organization's plan does not support this feature.");
}
}

private static void EnsureValidIds(Guid organizationId, Guid? reportId = null)
{
if (organizationId == Guid.Empty)
{
throw new BadRequestException("OrganizationId is required.");
}

if (reportId.HasValue && reportId.Value == Guid.Empty)
{
throw new BadRequestException("ReportId is required.");
}
}

private async Task<OrganizationReport> GetAuthorizedReportAsync(Guid organizationId, Guid reportId)
{
EnsureValidIds(organizationId, reportId);
await AuthorizeAsync(organizationId);
var report = await _getOrganizationReportQuery.GetOrganizationReportAsync(reportId);
if (report.OrganizationId != organizationId) throw new BadRequestException("Invalid report ID");
return report;
}


// Is being used by client on V2

[HttpGet("{organizationId}/data/summary/{reportId}")]
Expand Down Expand Up @@ -576,4 +539,40 @@ public async Task<IActionResult> UpdateOrganizationReportApplicationDataAsync(

return Ok(response);
}

private async Task AuthorizeAsync(Guid organizationId)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: all the same logic here, just moved private methods to the bottom of file to improve order

{
if (!await _currentContext.AccessReports(organizationId))
{
throw new NotFoundException();
}

var orgAbility = await _organizationAbilityCacheService.GetOrganizationAbilityAsync(organizationId);
if (orgAbility is null || !orgAbility.UseRiskInsights)
{
throw new BadRequestException("Your organization's plan does not support this feature.");
}
}

private static void EnsureValidIds(Guid organizationId, Guid? reportId = null)
{
if (organizationId == Guid.Empty)
{
throw new BadRequestException("OrganizationId is required.");
}

if (reportId.HasValue && reportId.Value == Guid.Empty)
{
throw new BadRequestException("ReportId is required.");
}
}

private async Task<OrganizationReport> GetAuthorizedReportAsync(Guid organizationId, Guid reportId)
{
EnsureValidIds(organizationId, reportId);
await AuthorizeAsync(organizationId);
var report = await _getOrganizationReportQuery.GetOrganizationReportAsync(reportId);
if (report.OrganizationId != organizationId) throw new BadRequestException("Invalid report ID");
return report;
}
}
64 changes: 23 additions & 41 deletions src/Api/Dirt/Controllers/ReportsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Bit.Api.Dirt.Models.Response;
using Bit.Api.Tools.Models.Response;
using Bit.Core;
using Bit.Core.AdminConsole.AbilitiesCache;
using Bit.Core.Context;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Reports.Models.Data;
Expand All @@ -26,6 +27,7 @@ public class ReportsController : Controller
private readonly IGetPasswordHealthReportApplicationQuery _getPwdHealthReportAppQuery;
private readonly IDropPasswordHealthReportApplicationCommand _dropPwdHealthReportAppCommand;
private readonly IGetPasskeyDirectoryQuery _getPasskeyDirectoryQuery;
private readonly IOrganizationAbilityCacheService _organizationAbilityCacheService;
private readonly ILogger<ReportsController> _logger;

public ReportsController(
Expand All @@ -36,6 +38,7 @@ public ReportsController(
IGetPasswordHealthReportApplicationQuery getPasswordHealthReportApplicationQuery,
IDropPasswordHealthReportApplicationCommand dropPwdHealthReportAppCommand,
IGetPasskeyDirectoryQuery getPasskeyDirectoryQuery,
IOrganizationAbilityCacheService organizationAbilityCacheService,
ILogger<ReportsController> logger
)
{
Expand All @@ -46,6 +49,7 @@ ILogger<ReportsController> logger
_getPwdHealthReportAppQuery = getPasswordHealthReportApplicationQuery;
_dropPwdHealthReportAppCommand = dropPwdHealthReportAppCommand;
_getPasskeyDirectoryQuery = getPasskeyDirectoryQuery;
_organizationAbilityCacheService = organizationAbilityCacheService;
_logger = logger;
}

Expand All @@ -59,12 +63,7 @@ ILogger<ReportsController> logger
[HttpGet("member-cipher-details/{orgId}")]
public async Task<IEnumerable<MemberCipherDetailsResponseModel>> GetMemberCipherDetails(Guid orgId)
{
// Using the AccessReports permission here until new permissions
// are needed for more control over reports
if (!await _currentContext.AccessReports(orgId))
{
throw new NotFoundException();
}
await AuthorizeAsync(orgId);

var riskDetails = await GetRiskInsightsReportDetails(new RiskInsightsReportRequest { OrganizationId = orgId });

Expand Down Expand Up @@ -125,39 +124,11 @@ private async Task<IEnumerable<RiskInsightsReportDetail>> GetRiskInsightsReportD
[HttpGet("password-health-report-applications/{orgId}")]
public async Task<IEnumerable<PasswordHealthReportApplication>> GetPasswordHealthReportApplications(Guid orgId)
{
if (!await _currentContext.AccessReports(orgId))
{
throw new NotFoundException();
}
await AuthorizeAsync(orgId);

return await _getPwdHealthReportAppQuery.GetPasswordHealthReportApplicationAsync(orgId);
}

/// <summary>
/// Adds a new record into PasswordHealthReportApplication
/// </summary>
/// <param name="request">A single instance of PasswordHealthReportApplication Model</param>
/// <returns>A single instance of PasswordHealthReportApplication</returns>
/// <exception cref="BadRequestException">If the organization Id is not valid</exception>
/// <exception cref="NotFoundException">If the user lacks access</exception>
[HttpPost("password-health-report-application")]
public async Task<PasswordHealthReportApplication> AddPasswordHealthReportApplication(
[FromBody] PasswordHealthReportApplicationModel request)
{
if (!await _currentContext.AccessReports(request.OrganizationId))
{
throw new NotFoundException();
}

var commandRequest = new AddPasswordHealthReportApplicationRequest
{
OrganizationId = request.OrganizationId,
Url = request.Url
};

return await _addPwdHealthReportAppCommand.AddPasswordHealthReportApplicationAsync(commandRequest);
}
Comment on lines -136 to -159

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this endpoint is removed as it is never called in the clients codebase. Only the endpoint for adding multiple critical applications at once is in use


/// <summary>
/// Adds multiple records into PasswordHealthReportApplication
/// </summary>
Expand All @@ -169,9 +140,9 @@ public async Task<PasswordHealthReportApplication> AddPasswordHealthReportApplic
public async Task<IEnumerable<PasswordHealthReportApplication>> AddPasswordHealthReportApplications(
[FromBody] IEnumerable<PasswordHealthReportApplicationModel> request)
{
if (request.Any(_ => _currentContext.AccessReports(_.OrganizationId).Result == false))
foreach (var item in request)
{
throw new NotFoundException();
await AuthorizeAsync(item.OrganizationId);
}

var commandRequests = request.Select(request => new AddPasswordHealthReportApplicationRequest
Expand All @@ -197,10 +168,7 @@ public async Task<IEnumerable<PasswordHealthReportApplication>> AddPasswordHealt
public async Task DropPasswordHealthReportApplication(
[FromBody] DropPasswordHealthReportApplicationRequest request)
{
if (!await _currentContext.AccessReports(request.OrganizationId))
{
throw new NotFoundException();
}
await AuthorizeAsync(request.OrganizationId);

await _dropPwdHealthReportAppCommand.DropPasswordHealthReportApplicationAsync(request);
}
Expand All @@ -222,4 +190,18 @@ public async Task<IEnumerable<PasskeyDirectoryResponseModel>> GetPasskeyDirector
Instructions = e.Instructions
});
}

private async Task AuthorizeAsync(Guid organizationId)
{
if (!await _currentContext.AccessReports(organizationId))
{
throw new NotFoundException();
}

var orgAbility = await _organizationAbilityCacheService.GetOrganizationAbilityAsync(organizationId);
if (orgAbility is null || !orgAbility.UseRiskInsights)
{
throw new BadRequestException("Your organization's plan does not support this feature.");
}
}
}
Loading
Loading