From d5d5fe0c48fdf31e76e8019f52e147c578b21211 Mon Sep 17 00:00:00 2001 From: Rui Tome Date: Fri, 28 Jul 2023 15:14:27 +0100 Subject: [PATCH 1/4] [PM-2844] Created ErrorMessages resource file --- src/Core/Core.csproj | 13 ++- .../Resources/ErrorMessages.en.Designer.cs | 102 ++++++++++++++++++ src/Core/Resources/ErrorMessages.en.resx | 48 +++++++++ 3 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 src/Core/Resources/ErrorMessages.en.Designer.cs create mode 100644 src/Core/Resources/ErrorMessages.en.resx diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 5a6b771c8fa2..a5538a262b98 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -17,6 +17,10 @@ + + ResXFileCodeGenerator + ErrorMessages.en.Designer.cs + @@ -60,7 +64,14 @@ - + + + + True + True + ErrorMessages.en.resx + + diff --git a/src/Core/Resources/ErrorMessages.en.Designer.cs b/src/Core/Resources/ErrorMessages.en.Designer.cs new file mode 100644 index 000000000000..6cdc5587f1cc --- /dev/null +++ b/src/Core/Resources/ErrorMessages.en.Designer.cs @@ -0,0 +1,102 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Bit.Core.Resources { + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class ErrorMessages_en { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal ErrorMessages_en() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("Bit.Core.Resources.ErrorMessages_en", typeof(ErrorMessages_en).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static string ERR001 { + get { + return ResourceManager.GetString("ERR001", resourceCulture); + } + } + + internal static string ERR002 { + get { + return ResourceManager.GetString("ERR002", resourceCulture); + } + } + + internal static string ERR003 { + get { + return ResourceManager.GetString("ERR003", resourceCulture); + } + } + + internal static string ERR000 { + get { + return ResourceManager.GetString("ERR000", resourceCulture); + } + } + + internal static string ERR004 { + get { + return ResourceManager.GetString("ERR004", resourceCulture); + } + } + + internal static string ERR005 { + get { + return ResourceManager.GetString("ERR005", resourceCulture); + } + } + + internal static string ERR006 { + get { + return ResourceManager.GetString("ERR006", resourceCulture); + } + } + + internal static string ERR500 { + get { + return ResourceManager.GetString("ERR500", resourceCulture); + } + } + + internal static string ERR007 { + get { + return ResourceManager.GetString("ERR007", resourceCulture); + } + } + } +} diff --git a/src/Core/Resources/ErrorMessages.en.resx b/src/Core/Resources/ErrorMessages.en.resx new file mode 100644 index 000000000000..ca24f376b590 --- /dev/null +++ b/src/Core/Resources/ErrorMessages.en.resx @@ -0,0 +1,48 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Organization not found. + + + This organization cannot use policies. + + + User not found. + + + An error has occurred. + + + Resource not found. + + + Invalid token. + + + Unauthorized. + + + An unhandled server error has occurred. + + + Trusted device encryption is on and requires this policy. + + \ No newline at end of file From d9f65bcf0ce4fa972241e91ecd5f6814be30e6fe Mon Sep 17 00:00:00 2001 From: Rui Tome Date: Fri, 28 Jul 2023 15:14:50 +0100 Subject: [PATCH 2/4] [PM-2844] Created constants class with error codes that match the resources file --- src/Core/Constants.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 5cd0349291b4..423726e74001 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -46,3 +46,16 @@ public static List GetAllKeys() .ToList(); } } + +public static class ErrorCodes +{ + public const string Error = "ERR000"; + public const string OrganizationNotFound = "ERR001"; + public const string OrganizationCannotUsePolicies = "ERR002"; + public const string UserNotFound = "ERR003"; + public const string ResourceNotFound = "ERR004"; + public const string InvalidToken = "ERR005"; + public const string Unauthorized = "ERR006"; + public const string PolicyRequiredByTrustedDeviceEncryption = "ERR007"; + public const string UnhandledError = "ERR500"; +} From e1f20ad3c697569f1100e7e6633bf29362ff3695 Mon Sep 17 00:00:00 2001 From: Rui Tome Date: Fri, 28 Jul 2023 15:15:45 +0100 Subject: [PATCH 3/4] [PM-2844] Updated ExceptionHandlerFilterAttribute to obtain error message from resource files if the error thrown is an error code --- .../ExceptionHandlerFilterAttribute.cs | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/Api/Utilities/ExceptionHandlerFilterAttribute.cs b/src/Api/Utilities/ExceptionHandlerFilterAttribute.cs index 4dadedeb3e6b..52d7746bd9dc 100644 --- a/src/Api/Utilities/ExceptionHandlerFilterAttribute.cs +++ b/src/Api/Utilities/ExceptionHandlerFilterAttribute.cs @@ -1,7 +1,11 @@ -using Bit.Api.Models.Public.Response; +using System.Reflection; +using Bit.Api.Models.Public.Response; +using Bit.Core; using Bit.Core.Exceptions; +using Bit.Core.Resources; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Localization; using Microsoft.IdentityModel.Tokens; using Stripe; using InternalApi = Bit.Core.Models.Api; @@ -19,7 +23,7 @@ public ExceptionHandlerFilterAttribute(bool publicApi) public override void OnException(ExceptionContext context) { - var errorMessage = "An error has occurred."; + var errorMessage = GetFormattedMessageFromErrorCode(context); var exception = context.Exception; if (exception == null) @@ -44,10 +48,6 @@ public override void OnException(ExceptionContext context) internalErrorModel = new InternalApi.ErrorResponseModel(badRequestException.ModelState); } } - else - { - errorMessage = badRequestException.Message; - } } else if (exception is StripeException stripeException && stripeException?.StripeError?.Type == "card_error") { @@ -65,12 +65,10 @@ public override void OnException(ExceptionContext context) } else if (exception is GatewayException) { - errorMessage = exception.Message; context.HttpContext.Response.StatusCode = 400; } else if (exception is NotSupportedException && !string.IsNullOrWhiteSpace(exception.Message)) { - errorMessage = exception.Message; context.HttpContext.Response.StatusCode = 400; } else if (exception is ApplicationException) @@ -79,17 +77,17 @@ public override void OnException(ExceptionContext context) } else if (exception is NotFoundException) { - errorMessage = "Resource not found."; + errorMessage = GetFormattedMessageFromErrorCode(context, ErrorCodes.ResourceNotFound); context.HttpContext.Response.StatusCode = 404; } else if (exception is SecurityTokenValidationException) { - errorMessage = "Invalid token."; + errorMessage = GetFormattedMessageFromErrorCode(context, ErrorCodes.InvalidToken); context.HttpContext.Response.StatusCode = 403; } else if (exception is UnauthorizedAccessException) { - errorMessage = "Unauthorized."; + errorMessage = GetFormattedMessageFromErrorCode(context, ErrorCodes.Unauthorized); context.HttpContext.Response.StatusCode = 401; } else if (exception is ConflictException) @@ -114,7 +112,7 @@ public override void OnException(ExceptionContext context) { var logger = context.HttpContext.RequestServices.GetRequiredService>(); logger.LogError(0, exception, exception.Message); - errorMessage = "An unhandled server error has occurred."; + errorMessage = GetFormattedMessageFromErrorCode(context, ErrorCodes.UnhandledError); context.HttpContext.Response.StatusCode = 500; } @@ -136,4 +134,23 @@ public override void OnException(ExceptionContext context) context.Result = new ObjectResult(errorModel); } } + + private string GetFormattedMessageFromErrorCode(ExceptionContext context, string alternativeErrorCode = null) + { + var localizerFactory = context.HttpContext.RequestServices.GetRequiredService(); + + var assemblyName = new AssemblyName(typeof(SharedResources).GetTypeInfo().Assembly.FullName); + var localizer = localizerFactory.Create("ErrorMessages", assemblyName.Name); + + var errorCode = alternativeErrorCode ?? context.Exception.Message; + var errorMessage = localizer[errorCode]; + + // Get localized error message for the exception message + if (errorMessage.ResourceNotFound is false) + { + return $"({errorCode}) {localizer[errorCode]}"; + } + + return context.Exception.Message; + } } From b3e41cdfbc0f5f8781a52fa2556f5db55d5cc19d Mon Sep 17 00:00:00 2001 From: Rui Tome Date: Fri, 28 Jul 2023 15:16:25 +0100 Subject: [PATCH 4/4] [PM-2844] Updated SyncController and PolicyService to use some ErrorCodes for exceptions --- src/Api/Vault/Controllers/SyncController.cs | 3 ++- src/Core/Services/Implementations/PolicyService.cs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Api/Vault/Controllers/SyncController.cs b/src/Api/Vault/Controllers/SyncController.cs index ec9ab4f96dd8..9c3383f3bbb6 100644 --- a/src/Api/Vault/Controllers/SyncController.cs +++ b/src/Api/Vault/Controllers/SyncController.cs @@ -1,4 +1,5 @@ using Bit.Api.Vault.Models.Response; +using Bit.Core; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Enums.Provider; @@ -59,7 +60,7 @@ public async Task Get([FromQuery] bool excludeDomains = false var user = await _userService.GetUserByPrincipalAsync(User); if (user == null) { - throw new BadRequestException("User not found."); + throw new BadRequestException(ErrorCodes.UserNotFound); } var organizationUserDetails = await _organizationUserRepository.GetManyDetailsByUserAsync(user.Id, diff --git a/src/Core/Services/Implementations/PolicyService.cs b/src/Core/Services/Implementations/PolicyService.cs index 6b1009093c1c..9b3512825632 100644 --- a/src/Core/Services/Implementations/PolicyService.cs +++ b/src/Core/Services/Implementations/PolicyService.cs @@ -49,7 +49,7 @@ public async Task SaveAsync(Policy policy, IUserService userService, IOrganizati if (!org.UsePolicies) { - throw new BadRequestException("This organization cannot use policies."); + throw new BadRequestException(ErrorCodes.OrganizationCannotUsePolicies); } // Handle dependent policy checks @@ -281,7 +281,7 @@ private async Task RequiredBySsoTrustedDeviceEncryptionAsync(Organization org) var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(org.Id); if (ssoConfig?.GetData()?.MemberDecryptionType == MemberDecryptionType.TrustedDeviceEncryption) { - throw new BadRequestException("Trusted device encryption is on and requires this policy."); + throw new BadRequestException(ErrorCodes.PolicyRequiredByTrustedDeviceEncryption); } } }