diff --git a/web/Areas/CTS/Controllers/BundleCompetencyController.cs b/web/Areas/CTS/Controllers/BundleCompetencyController.cs index f2ef217df..a3df4a6dc 100644 --- a/web/Areas/CTS/Controllers/BundleCompetencyController.cs +++ b/web/Areas/CTS/Controllers/BundleCompetencyController.cs @@ -5,6 +5,7 @@ using Viper.Classes.SQLContext; using Viper.Models.CTS; using Web.Authorization; +using Microsoft.Data.SqlClient; namespace Viper.Areas.CTS.Controllers { @@ -158,7 +159,7 @@ public async Task> DeleteBundleCompetency(int await context.SaveChangesAsync(); AdjustBundleCompetencyOrders(bundleComp); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return BadRequest("Cannot delete this bundle competency."); } diff --git a/web/Areas/CTS/Controllers/BundleCompetencyGroupController.cs b/web/Areas/CTS/Controllers/BundleCompetencyGroupController.cs index 2f64b08da..a9ed6330b 100644 --- a/web/Areas/CTS/Controllers/BundleCompetencyGroupController.cs +++ b/web/Areas/CTS/Controllers/BundleCompetencyGroupController.cs @@ -5,6 +5,7 @@ using Viper.Classes.SQLContext; using Viper.Models.CTS; using Web.Authorization; +using Microsoft.Data.SqlClient; namespace Viper.Areas.CTS.Controllers { @@ -116,7 +117,7 @@ public async Task> DeleteGroup(int bundle await context.SaveChangesAsync(); AdjustGroupOrders(group); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return BadRequest("Cannot delete group. Competencies must be removed from the group, and the group cannot have been used to document a student competency."); } diff --git a/web/Areas/CTS/Controllers/BundleController.cs b/web/Areas/CTS/Controllers/BundleController.cs index ac0bf44fc..45d8b0126 100644 --- a/web/Areas/CTS/Controllers/BundleController.cs +++ b/web/Areas/CTS/Controllers/BundleController.cs @@ -5,6 +5,7 @@ using Viper.Classes.SQLContext; using Viper.Models.CTS; using Web.Authorization; +using Microsoft.Data.SqlClient; namespace Viper.Areas.CTS.Controllers { @@ -139,7 +140,7 @@ public async Task> DeleteBundle(int bundleId) await context.SaveChangesAsync(); await trans.CommitAsync(); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return BadRequest("Could not delete bundle. If this bundle has been used, it cannot be deleted."); } diff --git a/web/Areas/CTS/Controllers/CompetencyController.cs b/web/Areas/CTS/Controllers/CompetencyController.cs index 027b5716d..5bad0e084 100644 --- a/web/Areas/CTS/Controllers/CompetencyController.cs +++ b/web/Areas/CTS/Controllers/CompetencyController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Viper.Areas.CTS.Models; using Viper.Classes; @@ -176,9 +177,9 @@ public async Task> DeleteCompetency(int competencyId { await context.SaveChangesAsync(); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { - return BadRequest("Could not remove domain. It may be linked to other objects."); + return BadRequest("Could not remove competency. It may be linked to other objects."); } await UpdateCompetencyOrders(); return new CompetencyDto(c); diff --git a/web/Areas/CTS/Controllers/CourseController.cs b/web/Areas/CTS/Controllers/CourseController.cs index 38b476c66..a0e487007 100644 --- a/web/Areas/CTS/Controllers/CourseController.cs +++ b/web/Areas/CTS/Controllers/CourseController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Viper.Areas.CTS.Models; using Viper.Classes; @@ -105,7 +106,7 @@ public async Task>> SetCourseRoles(int courseId, List await context.SaveChangesAsync(); await trans.CommitAsync(); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return BadRequest("Could not set roles."); } @@ -279,7 +280,7 @@ public async Task>> UpdateSessionCompete await context.SaveChangesAsync(); await trans.CommitAsync(); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return BadRequest("Could not update levels."); } diff --git a/web/Areas/CTS/Controllers/DomainController.cs b/web/Areas/CTS/Controllers/DomainController.cs index 2fc3fc16f..7f54773eb 100644 --- a/web/Areas/CTS/Controllers/DomainController.cs +++ b/web/Areas/CTS/Controllers/DomainController.cs @@ -5,6 +5,7 @@ using Viper.Classes.SQLContext; using Viper.Models.CTS; using Web.Authorization; +using Microsoft.Data.SqlClient; namespace Viper.Areas.CTS.Controllers { @@ -91,7 +92,7 @@ public async Task> DeleteDomain(int domainId) { await context.SaveChangesAsync(); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return BadRequest("Could not remove domain. It may be linked to other objects."); } diff --git a/web/Areas/CTS/Controllers/EpaController.cs b/web/Areas/CTS/Controllers/EpaController.cs index b63f716c5..9f0f1e009 100644 --- a/web/Areas/CTS/Controllers/EpaController.cs +++ b/web/Areas/CTS/Controllers/EpaController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Viper.Classes; using Viper.Classes.SQLContext; @@ -105,9 +106,9 @@ public async Task> GetEpa(int epaId) context.Entry(epa).State = EntityState.Deleted; await context.SaveChangesAsync(); } - catch (Exception ex) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { - return BadRequest(ex.Message); + return BadRequest("Could not delete EPA. It may be linked to other objects."); } return epa; diff --git a/web/Areas/CTS/Controllers/LevelsController.cs b/web/Areas/CTS/Controllers/LevelsController.cs index a11cf09b8..c6052f7ab 100644 --- a/web/Areas/CTS/Controllers/LevelsController.cs +++ b/web/Areas/CTS/Controllers/LevelsController.cs @@ -5,6 +5,7 @@ using Viper.Classes.SQLContext; using Viper.Models.CTS; using Web.Authorization; +using Microsoft.Data.SqlClient; namespace Viper.Areas.CTS.Controllers { @@ -151,7 +152,7 @@ public async Task DeleteLevel(int levelId) AdjustLevelOrders(existing); await trans.CommitAsync(); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return BadRequest("Could not delete level. If this level has been used in an assessment, it cannot be deleted."); } diff --git a/web/Areas/CTS/Controllers/RoleController.cs b/web/Areas/CTS/Controllers/RoleController.cs index 73ee123a4..9f04af8d7 100644 --- a/web/Areas/CTS/Controllers/RoleController.cs +++ b/web/Areas/CTS/Controllers/RoleController.cs @@ -4,6 +4,7 @@ using Viper.Classes; using Viper.Classes.SQLContext; using Web.Authorization; +using Microsoft.Data.SqlClient; namespace Viper.Areas.CTS.Controllers { @@ -84,7 +85,7 @@ public async Task> DeleteRole(int roleId) context.Entry(role).State = EntityState.Deleted; await context.SaveChangesAsync(); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return BadRequest("Could not delete role. If this role has been added to a bundle, it cannot be deleted."); } diff --git a/web/Areas/ClinicalScheduler/Controllers/CliniciansController.cs b/web/Areas/ClinicalScheduler/Controllers/CliniciansController.cs index a1c946817..a0c8b8c6c 100644 --- a/web/Areas/ClinicalScheduler/Controllers/CliniciansController.cs +++ b/web/Areas/ClinicalScheduler/Controllers/CliniciansController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Viper.Areas.ClinicalScheduler.Services; using Viper.Areas.Curriculum.Services; @@ -334,7 +335,7 @@ public async Task GetClinicianSchedule(string mothraId, [FromQuer schedules.Count, LogSanitizer.SanitizeId(mothraId), LogSanitizer.SanitizeYear(targetYear)); return Ok(result); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("MothraId", mothraId); @@ -381,7 +382,7 @@ public async Task GetClinicianRotations(string mothraId) _logger.LogDebug("Found {RotationCount} unique rotations for clinician {MothraId}", rotations.Count, LogSanitizer.SanitizeId(mothraId)); return Ok(rotations); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("MothraId", mothraId); @@ -575,7 +576,7 @@ private List FilterCliniciansByPermissions(IEnumerable AddInstructor( { return HandleInvalidOperation(ex, correlationId); } - catch (Exception ex) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { return HandleSystemError(ex, request.RotationId!.Value, correlationId); } @@ -335,7 +337,7 @@ public async Task RemoveInstructor( userMessage, correlationId)); } - catch (Exception ex) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { _logger.LogError(ex, "Error removing instructor schedule {ScheduleId} (CorrelationId: {CorrelationId})", instructorScheduleId, correlationId); @@ -414,7 +416,7 @@ public async Task SetPrimaryEvaluator( instructorScheduleId, correlationId); return Forbid(); } - catch (Exception ex) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { _logger.LogError(ex, "Error setting primary evaluator for instructor schedule {ScheduleId} (CorrelationId: {CorrelationId})", instructorScheduleId, correlationId); @@ -507,7 +509,7 @@ public async Task CheckScheduleConflicts( return Ok(response); } - catch (Exception ex) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { _logger.LogError(ex, "Error checking schedule conflicts for {MothraId}", LogSanitizer.SanitizeId(mothraId)); return StatusCode(500, "An error occurred while checking for schedule conflicts"); @@ -532,7 +534,7 @@ public async Task GetAuditHistory( var auditHistory = await _auditService.GetInstructorScheduleAuditHistoryAsync(instructorScheduleId, cancellationToken); return Ok(auditHistory); } - catch (Exception ex) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { _logger.LogError(ex, "Error retrieving audit history for instructor schedule {ScheduleId}", instructorScheduleId); return StatusCode(500, "An error occurred while retrieving audit history"); diff --git a/web/Areas/ClinicalScheduler/Controllers/PermissionsController.cs b/web/Areas/ClinicalScheduler/Controllers/PermissionsController.cs index 212ade12c..56e3ea450 100644 --- a/web/Areas/ClinicalScheduler/Controllers/PermissionsController.cs +++ b/web/Areas/ClinicalScheduler/Controllers/PermissionsController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; using Viper.Areas.ClinicalScheduler.Services; using Viper.Classes.SQLContext; @@ -153,7 +154,7 @@ public async Task> CanEditService(int serviceId) return Ok(new { canEdit }); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("ServiceId", serviceId); @@ -215,7 +216,7 @@ public async Task> CanEditRotation(int rotationId) return Ok(new { canEdit }); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("RotationId", rotationId); @@ -277,7 +278,7 @@ public async Task> CanEditOwnSchedule(int instructorSchedul return Ok(new { canEditOwn }); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("InstructorScheduleId", instructorScheduleId); diff --git a/web/Areas/ClinicalScheduler/Controllers/RotationsController.cs b/web/Areas/ClinicalScheduler/Controllers/RotationsController.cs index 2e0736fd5..e0bf5c64b 100644 --- a/web/Areas/ClinicalScheduler/Controllers/RotationsController.cs +++ b/web/Areas/ClinicalScheduler/Controllers/RotationsController.cs @@ -8,6 +8,7 @@ using Viper.Models.ClinicalScheduler; using Web.Authorization; using Person = Viper.Models.ClinicalScheduler.Person; +using Microsoft.Data.SqlClient; namespace Viper.Areas.ClinicalScheduler.Controllers { @@ -70,7 +71,7 @@ public async Task>> GetRotations(int? serv rotations.Count, filteredRotations.Count); return Ok(filteredRotations); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext(new Dictionary @@ -131,7 +132,7 @@ public async Task> GetRotation(int id) _logger.LogInformation("Retrieved rotation via RotationService: {RotationName}", rotation.Name); return Ok(response); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("RotationId", id); @@ -233,7 +234,7 @@ public async Task> GetRotationSchedule(int id, [FromQuery] return Ok(BuildRotationScheduleResponse(rotation, targetYear, groupedSchedules, recentCliniciansList)); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("RotationId", id); @@ -298,7 +299,7 @@ join w in _context.Weeks on i.WeekId equals w.WeekId _logger.LogInformation("Retrieved {Count} rotations with scheduled weeks for year {Year} (filtered to {FilteredCount})", rotationsWithSchedules.Count, LogSanitizer.SanitizeYear(targetYear), filteredRotations.Count); return Ok(filteredRotations); } - catch (Exception) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { // Store context for ApiExceptionFilter to use in logging SetExceptionContext("Year", year?.ToString() ?? "null"); diff --git a/web/Areas/Computing/Services/BiorenderStudentLookup.cs b/web/Areas/Computing/Services/BiorenderStudentLookup.cs index ad8748394..6a8600ddd 100644 --- a/web/Areas/Computing/Services/BiorenderStudentLookup.cs +++ b/web/Areas/Computing/Services/BiorenderStudentLookup.cs @@ -95,7 +95,7 @@ static private bool IsValidEmail(string email) var addr = new MailAddress(email); return addr.Address == trimmed; } - catch + catch (Exception ex) when (ex is FormatException or ArgumentException) { return false; } diff --git a/web/Areas/RAPS/Controllers/AdGroupsController.cs b/web/Areas/RAPS/Controllers/AdGroupsController.cs index 46ad69752..106d9dd03 100644 --- a/web/Areas/RAPS/Controllers/AdGroupsController.cs +++ b/web/Areas/RAPS/Controllers/AdGroupsController.cs @@ -10,6 +10,7 @@ using Viper.Classes.Utilities; using Viper.Models.RAPS; using Web.Authorization; +using Microsoft.Data.SqlClient; namespace Viper.Areas.RAPS.Controllers { @@ -113,10 +114,10 @@ public async Task> CreateGroup(GroupAddEdit group) OuGroup newOuGroup = await _ouGroupService.CreateRapsGroup(group.Name, group.Description); return CreatedAtAction("CreateGroup", new { id = newOuGroup.OugroupId }, newOuGroup); } - catch (Exception ex) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { - //Exception message could be indication user is trying to create a group that exists or is invalid. - return ValidationProblem(ex.Message); + // Return a generic message; the raw exception may reveal internal details. + return ValidationProblem("Unable to create or update the group. Verify the request and try again."); } } diff --git a/web/Areas/RAPS/Controllers/RoleMembersController.cs b/web/Areas/RAPS/Controllers/RoleMembersController.cs index 2ac6be1e8..7981bb98a 100644 --- a/web/Areas/RAPS/Controllers/RoleMembersController.cs +++ b/web/Areas/RAPS/Controllers/RoleMembersController.cs @@ -267,7 +267,7 @@ private static void UpdateTblRoleMemberWithDto(TblRoleMember tblRoleMember, Role return vmacsResponse; } } - catch + catch (JsonException) { return new VmacsResponse { diff --git a/web/Areas/RAPS/Controllers/RolesController.cs b/web/Areas/RAPS/Controllers/RolesController.cs index a64bf9f61..121981bec 100644 --- a/web/Areas/RAPS/Controllers/RolesController.cs +++ b/web/Areas/RAPS/Controllers/RolesController.cs @@ -6,6 +6,7 @@ using Viper.Classes; using Viper.Classes.SQLContext; using Viper.Models.RAPS; +using Microsoft.Data.SqlClient; namespace Viper.Areas.RAPS.Controllers { @@ -218,13 +219,13 @@ public async Task> PostTblRole(string instance, RoleCreate return CreatedAtAction("GetTblRole", new { id = tblRole.RoleId }, tblRole); } - catch (DbUpdateConcurrencyException ex) + catch (DbUpdateConcurrencyException) { - return Problem("The record was not updated because it was locked. " + ex.InnerException?.Message); + return Problem("The record was not updated because it was locked."); } - catch (Exception ex) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException) { - return Problem("There was a problem updating the database. " + ex.InnerException?.Message); + return Problem("There was a problem updating the database."); } } diff --git a/web/Areas/RAPS/Services/OuGroupService.cs b/web/Areas/RAPS/Services/OuGroupService.cs index 9b63be614..48594de50 100644 --- a/web/Areas/RAPS/Services/OuGroupService.cs +++ b/web/Areas/RAPS/Services/OuGroupService.cs @@ -1,3 +1,4 @@ +using System.DirectoryServices.Protocols; using System.Runtime.Versioning; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; @@ -43,9 +44,9 @@ public async Task> GetAllGroups(string? search) List ldapGroups = new(); try { - ActiveDirectoryService.GetGroups(); + ldapGroups = ActiveDirectoryService.GetGroups(); } - catch (Exception ex) + catch (Exception ex) when (ex is LdapException or DirectoryOperationException) { Logger logger = LogManager.GetCurrentClassLogger(); logger.Error(ex); @@ -53,14 +54,21 @@ public async Task> GetAllGroups(string? search) List managedGroups = await _uInformService.GetManagedGroups(); + HashSet boxSyncGroupDns = ldapGroups + .Where(lg => lg.ExtensionAttribute3?.ToLower() == "ucdboxsync") + .Select(lg => lg.DistinguishedName.ToLower()) + .ToHashSet(); + Dictionary displayNamesByDn = managedGroups + .GroupBy(mg => mg.DistinguishedName.ToLower()) + .ToDictionary(g => g.Key, g => g.First().DisplayName); + List groups = new(); foreach (OuGroup group in result) { //for OU groups, look up the box sync attribute - bool BoxSync = ldapGroups.FirstOrDefault(lg => lg.DistinguishedName.ToLower() == group.Name.ToLower()) - ?.ExtensionAttribute3?.ToLower() == "ucdboxsync"; + bool BoxSync = boxSyncGroupDns.Contains(group.Name.ToLower()); //for AD3 groups, look up display name - string? displayName = managedGroups.FirstOrDefault(mg => mg.DistinguishedName.ToLower() == group.Name.ToLower())?.DisplayName; + string? displayName = displayNamesByDn.GetValueOrDefault(group.Name.ToLower()); Group g = new(group) { BoxSyncEnabled = BoxSync, diff --git a/web/Areas/RAPS/Services/VMACSExport.cs b/web/Areas/RAPS/Services/VMACSExport.cs index 856a26a55..95c2d86ca 100644 --- a/web/Areas/RAPS/Services/VMACSExport.cs +++ b/web/Areas/RAPS/Services/VMACSExport.cs @@ -132,7 +132,7 @@ public async Task> ExportToVMACS(string instance, string? server = VmacsResponse vmacsResponse = await ParseResponse(response); RecordMessage(messages, JsonSerializer.Serialize(vmacsResponse)); } - catch (Exception ex) + catch (Exception ex) when (ex is HttpRequestException or InvalidOperationException or OperationCanceledException) { HttpHelper.Logger.Log(LogLevel.Warn, ex); RecordMessage(messages, "Error: " + ex.Message + " " + ex.StackTrace); @@ -167,7 +167,7 @@ private static async Task ParseResponse(HttpResponseMessage respo vmacsResponse.Success = response.IsSuccessStatusCode; } - catch (Exception) + catch (Exception ex) when (ex is JsonException or NotSupportedException or InvalidOperationException) { vmacsResponse = new() { diff --git a/web/Classes/ClaimsTransformer.cs b/web/Classes/ClaimsTransformer.cs index d39fa2c81..550d58d17 100644 --- a/web/Classes/ClaimsTransformer.cs +++ b/web/Classes/ClaimsTransformer.cs @@ -1,6 +1,8 @@ using System.Security.Claims; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.DataProtection; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Viper; using Viper.Classes; @@ -90,7 +92,7 @@ public Task TransformAsync(ClaimsPrincipal principal) } - catch (Exception ex) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { bool isProd = false; diff --git a/web/Classes/SitemapMiddleware.cs b/web/Classes/SitemapMiddleware.cs index 6a85e3770..6c5f00556 100644 --- a/web/Classes/SitemapMiddleware.cs +++ b/web/Classes/SitemapMiddleware.cs @@ -85,7 +85,12 @@ public async Task Invoke(HttpContext context) await memoryStream.CopyToAsync(stream, bytes.Length); } } + // Middleware boundary: any sitemap-generation failure (DB, IO, + // reflection, etc.) must fall through to the pipeline rather than + // break the request. +#pragma warning disable CA1031 catch (Exception) +#pragma warning restore CA1031 { await _next(context); } diff --git a/web/Classes/UserHelper.cs b/web/Classes/UserHelper.cs index 147bd8f07..106e569ae 100644 --- a/web/Classes/UserHelper.cs +++ b/web/Classes/UserHelper.cs @@ -5,6 +5,8 @@ using Viper.Models.AAUD; using Viper.Models.RAPS; using Web.Authorization; +using Microsoft.EntityFrameworkCore; +using Microsoft.Data.SqlClient; namespace Viper { @@ -255,7 +257,7 @@ public bool HasPermission(RAPSContext? rapsContext, AaudUser? user, string permi return user; } } - catch (Exception ex) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { HttpHelper.Logger.Error(ex); return null; @@ -282,7 +284,7 @@ public bool HasPermission(RAPSContext? rapsContext, AaudUser? user, string permi return currentUser; } - catch (Exception ex) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { HttpHelper.Logger.Error(ex); return null; @@ -314,7 +316,7 @@ public bool HasPermission(RAPSContext? rapsContext, AaudUser? user, string permi return GetCurrentUser(); } - catch (Exception ex) + catch (Exception ex) when (ex is DbUpdateException or SqlException or InvalidOperationException or OperationCanceledException) { HttpHelper.Logger.Error(ex); return null; diff --git a/web/Classes/Utilities/ActiveDirectoryService.cs b/web/Classes/Utilities/ActiveDirectoryService.cs index a2fc262fc..d0aa38a06 100644 --- a/web/Classes/Utilities/ActiveDirectoryService.cs +++ b/web/Classes/Utilities/ActiveDirectoryService.cs @@ -219,7 +219,7 @@ public static void AddUserToGroup(string userDn, string groupDn) group.Save(); } } - catch (Exception ex) + catch (Exception ex) when (ex is PrincipalException or System.DirectoryServices.DirectoryServicesCOMException) { HttpHelper.Logger.Error(ex); } @@ -245,7 +245,7 @@ public static void RemoveUserFromGroup(string userDn, string groupDn) group.Save(); } } - catch (Exception ex) + catch (Exception ex) when (ex is PrincipalException or System.DirectoryServices.DirectoryServicesCOMException) { HttpHelper.Logger.Error(ex); } diff --git a/web/Classes/Utilities/F5HttpRequest.cs b/web/Classes/Utilities/F5HttpRequest.cs index 9b9167b42..fd487433e 100644 --- a/web/Classes/Utilities/F5HttpRequest.cs +++ b/web/Classes/Utilities/F5HttpRequest.cs @@ -22,7 +22,7 @@ public async Task Send(HttpRequestMessage request, int atte { response = await _httpClient.SendAsync(request); } - catch (Exception) + catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException) { response = await HandleConnectionFail(request, attemptNumber); } diff --git a/web/Classes/Utilities/IamApi.cs b/web/Classes/Utilities/IamApi.cs index cdd07a1ca..cda193007 100644 --- a/web/Classes/Utilities/IamApi.cs +++ b/web/Classes/Utilities/IamApi.cs @@ -398,7 +398,7 @@ static private async Task> ParseResponse(HttpResponseMessage? res } } - catch (Exception ex) + catch (Exception ex) when (ex is JsonException or NotSupportedException or InvalidOperationException or OperationCanceledException) { r.ErrorMessage = "Invalid response: " + ex.Message + "."; } diff --git a/web/Program.cs b/web/Program.cs index 6b65c380f..8f87dc1e7 100644 --- a/web/Program.cs +++ b/web/Program.cs @@ -6,6 +6,7 @@ using System.Xml.Linq; using Amazon; using Amazon.Extensions.NETCore.Setup; +using Amazon.Runtime; using Amazon.Runtime.CredentialManagement; using DotNetEnv; using Joonasw.AspNetCore.SecurityHeaders; @@ -86,7 +87,7 @@ .AddSystemsManager("/" + builder.Environment.EnvironmentName, awsOptions) .AddSystemsManager("/Shared", awsOptions); } - catch (Exception ex) + catch (Exception ex) when (ex is AmazonServiceException or AmazonClientException) { logger.Fatal(ex, "Failed to get secrets from AWS"); } @@ -519,7 +520,9 @@ void RegisterDbContext(string connectionStringKey) where TContext : Db app.Run(); #pragma warning restore S6966 } +#pragma warning disable CA1031 // Top-level app startup must catch any exception to log fatal and rethrow as InvalidOperationException with context for hosting platform. catch (Exception exception) +#pragma warning restore CA1031 { // NLog: catch setup errors logger.Fatal(exception, "Stopped program because of exception"); @@ -565,10 +568,9 @@ void SetAwsCredentials(Logger logger) { File.Delete(awsCredentialsFilePath); } - catch (Exception ex) + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { logger.Error(ex, $"COULD NOT DELETE THE AWS CREDENTIALS XML FILE (\"{awsCredentialsFilePath}\"). The file will need to be deleted manually."); - logger.Error(ex, $"COULD NOT DELETE THE AWS CREDENTIALS XML FILE (\"{awsCredentialsFilePath}\"). The file will need to be deleted manually."); } } else diff --git a/web/Services/EmailService.cs b/web/Services/EmailService.cs index 61c3d73a3..8c91b75d1 100644 --- a/web/Services/EmailService.cs +++ b/web/Services/EmailService.cs @@ -219,7 +219,7 @@ public async Task IsServiceAvailableAsync() // For production, assume the SMTP server is available return true; } - catch (Exception ex) + catch (Exception ex) when (ex is SocketException or ArgumentException or InvalidOperationException) { _logger.LogError(ex, "Error checking email service availability"); return false; @@ -246,7 +246,7 @@ private async Task IsMailpitAvailableAsync() await tcpClient.ConnectAsync(_emailSettings.SmtpHost, _emailSettings.SmtpPort); return tcpClient.Connected; } - catch + catch (SocketException) { return false; } diff --git a/web/ViteProxyHelpers.cs b/web/ViteProxyHelpers.cs index 31838b510..2857ed170 100644 --- a/web/ViteProxyHelpers.cs +++ b/web/ViteProxyHelpers.cs @@ -284,7 +284,7 @@ public static async Task CopyProxyResponse(HttpContext context, HttpResponseMess { context.Response.Headers[header.Key] = header.Value.ToArray(); } - catch (Exception headerEx) + catch (Exception headerEx) when (headerEx is ArgumentException or InvalidOperationException) { // Use structured logging for header errors var safeHeaderKey = WebUtility.HtmlEncode(header.Key); @@ -367,7 +367,7 @@ public static async Task HandleProxyError(HttpContext context, Exception ex, ILo return; } } - catch (Exception fileEx) + catch (Exception fileEx) when (fileEx is IOException or UnauthorizedAccessException or InvalidOperationException) { var safePath = context.Request.Path.ToString().Replace("\r", "").Replace("\n", ""); logger.LogWarning(fileEx, "Failed to serve static file fallback for {Path}", safePath);