using Bit.Api.Dirt.Models; using Bit.Api.Dirt.Models.Response; using Bit.Api.Tools.Models.Response; using Bit.Core.Context; using Bit.Core.Dirt.Reports.Entities; using Bit.Core.Dirt.Reports.Models.Data; using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces; using Bit.Core.Dirt.Reports.ReportFeatures.OrganizationReportMembers.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")] [Authorize("Application")] public class ReportsController : Controller { private readonly ICurrentContext _currentContext; private readonly IMemberAccessReportQuery _memberAccessReportQuery; private readonly IRiskInsightsReportQuery _riskInsightsReportQuery; private readonly IAddPasswordHealthReportApplicationCommand _addPwdHealthReportAppCommand; private readonly IGetPasswordHealthReportApplicationQuery _getPwdHealthReportAppQuery; private readonly IDropPasswordHealthReportApplicationCommand _dropPwdHealthReportAppCommand; public ReportsController( ICurrentContext currentContext, IMemberAccessReportQuery memberAccessReportQuery, IRiskInsightsReportQuery riskInsightsReportQuery, IAddPasswordHealthReportApplicationCommand addPasswordHealthReportApplicationCommand, IGetPasswordHealthReportApplicationQuery getPasswordHealthReportApplicationQuery, IDropPasswordHealthReportApplicationCommand dropPwdHealthReportAppCommand ) { _currentContext = currentContext; _memberAccessReportQuery = memberAccessReportQuery; _riskInsightsReportQuery = riskInsightsReportQuery; _addPwdHealthReportAppCommand = addPasswordHealthReportApplicationCommand; _getPwdHealthReportAppQuery = getPasswordHealthReportApplicationQuery; _dropPwdHealthReportAppCommand = dropPwdHealthReportAppCommand; } /// /// Organization member information containing a list of cipher ids /// assigned /// /// Organzation Id /// IEnumerable of MemberCipherDetailsResponseModel /// If Access reports permission is not assigned [HttpGet("member-cipher-details/{orgId}")] public async Task> 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(); } var riskDetails = await GetRiskInsightsReportDetails(new RiskInsightsReportRequest { OrganizationId = orgId }); var responses = riskDetails.Select(x => new MemberCipherDetailsResponseModel(x)); return responses; } /// /// Access details for an organization member. Includes the member information, /// group collection assignment, and item counts /// /// Organization Id /// IEnumerable of MemberAccessReportResponseModel /// If Access reports permission is not assigned [HttpGet("member-access/{orgId}")] public async Task> GetMemberAccessReport(Guid orgId) { if (!await _currentContext.AccessReports(orgId)) { throw new NotFoundException(); } var accessDetails = await GetMemberAccessDetails(new MemberAccessReportRequest { OrganizationId = orgId }); var responses = accessDetails.Select(x => new MemberAccessDetailReportResponseModel(x)); return responses; } /// /// Contains the organization member info, the cipher ids associated with the member, /// and details on their collections, groups, and permissions /// /// Request parameters /// /// List of a user's permissions at a group and collection level as well as the number of ciphers /// associated with that group/collection /// private async Task> GetMemberAccessDetails( MemberAccessReportRequest request) { var accessDetails = await _memberAccessReportQuery.GetMemberAccessReportsAsync(request); return accessDetails; } /// /// Gets the risk insights report details from the risk insights query. Associates a user to their cipher ids /// /// Request parameters /// A list of risk insights data associating the user to cipher ids private async Task> GetRiskInsightsReportDetails( RiskInsightsReportRequest request) { var riskDetails = await _riskInsightsReportQuery.GetRiskInsightsReportDetails(request); return riskDetails; } /// /// Get the password health report applications for an organization /// /// A valid Organization Id /// An Enumerable of PasswordHealthReportApplication /// If the user lacks access /// If the organization Id is not valid [HttpGet("password-health-report-applications/{orgId}")] public async Task> GetPasswordHealthReportApplications(Guid orgId) { if (!await _currentContext.AccessReports(orgId)) { throw new NotFoundException(); } return await _getPwdHealthReportAppQuery.GetPasswordHealthReportApplicationAsync(orgId); } /// /// Adds a new record into PasswordHealthReportApplication /// /// A single instance of PasswordHealthReportApplication Model /// A single instance of PasswordHealthReportApplication /// If the organization Id is not valid /// If the user lacks access [HttpPost("password-health-report-application")] public async Task 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); } /// /// Adds multiple records into PasswordHealthReportApplication /// /// A enumerable of PasswordHealthReportApplicationModel /// An Enumerable of PasswordHealthReportApplication /// If user does not have access to the OrganizationId /// If the organization Id is not valid [HttpPost("password-health-report-applications")] public async Task> AddPasswordHealthReportApplications( [FromBody] IEnumerable request) { if (request.Any(_ => _currentContext.AccessReports(_.OrganizationId).Result == false)) { throw new NotFoundException(); } var commandRequests = request.Select(request => new AddPasswordHealthReportApplicationRequest { OrganizationId = request.OrganizationId, Url = request.Url }).ToList(); return await _addPwdHealthReportAppCommand.AddPasswordHealthReportApplicationAsync(commandRequests); } /// /// Drops a record from PasswordHealthReportApplication /// /// /// A single instance of DropPasswordHealthReportApplicationRequest /// { OrganizationId, array of PasswordHealthReportApplicationIds } /// /// /// If user does not have access to the organization /// If the organization does not have any records [HttpDelete("password-health-report-application")] public async Task DropPasswordHealthReportApplication( [FromBody] DropPasswordHealthReportApplicationRequest request) { if (!await _currentContext.AccessReports(request.OrganizationId)) { throw new NotFoundException(); } await _dropPwdHealthReportAppCommand.DropPasswordHealthReportApplicationAsync(request); } }