diff --git a/bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs b/bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs index e2e5437a55..3edde8a6ae 100644 --- a/bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs +++ b/bitwarden_license/src/Commercial.Core/AdminConsole/Services/ProviderService.cs @@ -257,7 +257,7 @@ public class ProviderService : IProviderService await _providerUserRepository.ReplaceAsync(providerUser); events.Add((providerUser, EventType.ProviderUser_Confirmed, null)); - await _mailService.SendProviderConfirmedEmailAsync(provider.Name, user.Email); + await _mailService.SendProviderConfirmedEmailAsync(provider.DisplayName(), user.Email); result.Add(Tuple.Create(providerUser, "")); } catch (BadRequestException e) @@ -331,7 +331,7 @@ public class ProviderService : IProviderService var email = user == null ? providerUser.Email : user.Email; if (!string.IsNullOrWhiteSpace(email)) { - await _mailService.SendProviderUserRemoved(provider.Name, email); + await _mailService.SendProviderUserRemoved(provider.DisplayName(), email); } result.Add(Tuple.Create(providerUser, "")); @@ -586,7 +586,7 @@ public class ProviderService : IProviderService var nowMillis = CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow); var token = _dataProtector.Protect( $"ProviderUserInvite {providerUser.Id} {providerUser.Email} {nowMillis}"); - await _mailService.SendProviderInviteEmailAsync(provider.Name, providerUser, token, providerUser.Email); + await _mailService.SendProviderInviteEmailAsync(provider.DisplayName(), providerUser, token, providerUser.Email); } private async Task HasConfirmedProviderAdminExceptAsync(Guid providerId, IEnumerable providerUserIds) diff --git a/bitwarden_license/src/Sso/Controllers/AccountController.cs b/bitwarden_license/src/Sso/Controllers/AccountController.cs index cbee7ed7d5..f940138280 100644 --- a/bitwarden_license/src/Sso/Controllers/AccountController.cs +++ b/bitwarden_license/src/Sso/Controllers/AccountController.cs @@ -26,6 +26,7 @@ using IdentityModel; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using AuthenticationSchemes = Bit.Core.AuthenticationSchemes; using DIM = Duende.IdentityServer.Models; namespace Bit.Sso.Controllers; @@ -483,7 +484,7 @@ public class AccountController : Controller if (orgUser.Status == OrganizationUserStatusType.Invited) { // Org User is invited - they must manually accept the invite via email and authenticate with MP - throw new Exception(_i18nService.T("UserAlreadyInvited", email, organization.Name)); + throw new Exception(_i18nService.T("UserAlreadyInvited", email, organization.DisplayName())); } // Accepted or Confirmed - create SSO link and return; @@ -516,7 +517,7 @@ public class AccountController : Controller await _organizationService.AdjustSeatsAsync(orgId, initialSeatCount - organization.Seats.Value, prorationDate); } _logger.LogInformation(e, "SSO auto provisioning failed"); - throw new Exception(_i18nService.T("NoSeatsAvailable", organization.Name)); + throw new Exception(_i18nService.T("NoSeatsAvailable", organization.DisplayName())); } } } diff --git a/src/Admin/AdminConsole/Controllers/OrganizationsController.cs b/src/Admin/AdminConsole/Controllers/OrganizationsController.cs index ac4682edb6..cb64ea2d42 100644 --- a/src/Admin/AdminConsole/Controllers/OrganizationsController.cs +++ b/src/Admin/AdminConsole/Controllers/OrganizationsController.cs @@ -1,4 +1,5 @@ -using Bit.Admin.AdminConsole.Models; +using System.Net; +using Bit.Admin.AdminConsole.Models; using Bit.Admin.Enums; using Bit.Admin.Services; using Bit.Admin.Utilities; @@ -119,8 +120,9 @@ public class OrganizationsController : Controller count = 1; } + var encodedName = WebUtility.HtmlEncode(name); var skip = (page - 1) * count; - var organizations = await _organizationRepository.SearchAsync(name, userEmail, paid, skip, count); + var organizations = await _organizationRepository.SearchAsync(encodedName, userEmail, paid, skip, count); return View(new OrganizationsModel { Items = organizations as List, diff --git a/src/Admin/AdminConsole/Controllers/ProvidersController.cs b/src/Admin/AdminConsole/Controllers/ProvidersController.cs index c228149ea2..47631829ed 100644 --- a/src/Admin/AdminConsole/Controllers/ProvidersController.cs +++ b/src/Admin/AdminConsole/Controllers/ProvidersController.cs @@ -1,4 +1,5 @@ -using Bit.Admin.AdminConsole.Models; +using System.Net; +using Bit.Admin.AdminConsole.Models; using Bit.Admin.Enums; using Bit.Admin.Utilities; using Bit.Core; @@ -188,8 +189,9 @@ public class ProvidersController : Controller count = 1; } + var encodedName = WebUtility.HtmlEncode(name); var skip = (page - 1) * count; - var unassignedOrganizations = await _organizationRepository.SearchUnassignedToProviderAsync(name, ownerEmail, skip, count); + var unassignedOrganizations = await _organizationRepository.SearchUnassignedToProviderAsync(encodedName, ownerEmail, skip, count); var viewModel = new OrganizationUnassignedToProviderSearchViewModel { OrganizationName = string.IsNullOrWhiteSpace(name) ? null : name, @@ -199,7 +201,7 @@ public class ProvidersController : Controller Items = unassignedOrganizations.Select(uo => new OrganizationSelectableViewModel { Id = uo.Id, - Name = uo.Name, + Name = uo.DisplayName(), PlanType = uo.PlanType }).ToList() }; diff --git a/src/Admin/AdminConsole/Models/OrganizationEditModel.cs b/src/Admin/AdminConsole/Models/OrganizationEditModel.cs index 575bd34e46..84923a9595 100644 --- a/src/Admin/AdminConsole/Models/OrganizationEditModel.cs +++ b/src/Admin/AdminConsole/Models/OrganizationEditModel.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Net; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Enums.Provider; @@ -36,8 +37,8 @@ public class OrganizationEditModel : OrganizationViewModel BillingInfo = billingInfo; BraintreeMerchantId = globalSettings.Braintree.MerchantId; - Name = org.Name; - BusinessName = org.BusinessName; + Name = org.DisplayName(); + BusinessName = org.DisplayBusinessName(); BillingEmail = provider?.Type == ProviderType.Reseller ? provider.BillingEmail : org.BillingEmail; PlanType = org.PlanType; Plan = org.Plan; @@ -184,8 +185,8 @@ public class OrganizationEditModel : OrganizationViewModel public Organization ToOrganization(Organization existingOrganization) { - existingOrganization.Name = Name; - existingOrganization.BusinessName = BusinessName; + existingOrganization.Name = WebUtility.HtmlEncode(Name.Trim()); + existingOrganization.BusinessName = WebUtility.HtmlEncode(BusinessName.Trim()); existingOrganization.BillingEmail = BillingEmail?.ToLowerInvariant()?.Trim(); existingOrganization.PlanType = PlanType.Value; existingOrganization.Plan = Plan; diff --git a/src/Admin/AdminConsole/Models/ProviderEditModel.cs b/src/Admin/AdminConsole/Models/ProviderEditModel.cs index c00bdea6e5..7480a24b35 100644 --- a/src/Admin/AdminConsole/Models/ProviderEditModel.cs +++ b/src/Admin/AdminConsole/Models/ProviderEditModel.cs @@ -11,8 +11,8 @@ public class ProviderEditModel : ProviderViewModel public ProviderEditModel(Provider provider, IEnumerable providerUsers, IEnumerable organizations) : base(provider, providerUsers, organizations) { - Name = provider.Name; - BusinessName = provider.BusinessName; + Name = provider.DisplayName(); + BusinessName = provider.DisplayBusinessName(); BillingEmail = provider.BillingEmail; BillingPhone = provider.BillingPhone; } diff --git a/src/Admin/AdminConsole/Views/Organizations/Edit.cshtml b/src/Admin/AdminConsole/Views/Organizations/Edit.cshtml index bed7c789ee..23ba63bbeb 100644 --- a/src/Admin/AdminConsole/Views/Organizations/Edit.cshtml +++ b/src/Admin/AdminConsole/Views/Organizations/Edit.cshtml @@ -4,7 +4,7 @@ @inject Bit.Admin.Services.IAccessControlService AccessControlService @model OrganizationEditModel @{ - ViewData["Title"] = (Model.Provider != null ? "Client " : string.Empty) + "Organization: " + Model.Organization.Name; + ViewData["Title"] = (Model.Provider != null ? "Client " : string.Empty) + "Organization: " + Model.Name; var canViewOrganizationInformation = AccessControlService.UserHasPermission(Permission.Org_OrgInformation_View); var canViewBillingInformation = AccessControlService.UserHasPermission(Permission.Org_BillingInformation_View); @@ -58,7 +58,7 @@ } -

@(Model.Provider != null ? "Client " : string.Empty)Organization @Model.Organization.Name

+

@(Model.Provider != null ? "Client " : string.Empty)Organization @Model.Name

@if (Model.Provider != null) { diff --git a/src/Admin/AdminConsole/Views/Organizations/Index.cshtml b/src/Admin/AdminConsole/Views/Organizations/Index.cshtml index b68aea05d7..7036b0cc0e 100644 --- a/src/Admin/AdminConsole/Views/Organizations/Index.cshtml +++ b/src/Admin/AdminConsole/Views/Organizations/Index.cshtml @@ -46,7 +46,7 @@ { - @org.Name + @org.DisplayName() @org.Plan diff --git a/src/Admin/AdminConsole/Views/Organizations/View.cshtml b/src/Admin/AdminConsole/Views/Organizations/View.cshtml index 77a76a021e..cb582cba53 100644 --- a/src/Admin/AdminConsole/Views/Organizations/View.cshtml +++ b/src/Admin/AdminConsole/Views/Organizations/View.cshtml @@ -1,10 +1,10 @@ @inject Bit.Core.Settings.GlobalSettings GlobalSettings @model OrganizationViewModel @{ - ViewData["Title"] = "Organization: " + Model.Organization.Name; + ViewData["Title"] = "Organization: " + Model.Organization.DisplayName(); } -

Organization @Model.Organization.Name

+

Organization @Model.Organization.DisplayName()

@if (Model.Provider != null) { diff --git a/src/Admin/AdminConsole/Views/Organizations/_ProviderInformation.cshtml b/src/Admin/AdminConsole/Views/Organizations/_ProviderInformation.cshtml index 4bd2118ea0..03ecad452d 100644 --- a/src/Admin/AdminConsole/Views/Organizations/_ProviderInformation.cshtml +++ b/src/Admin/AdminConsole/Views/Organizations/_ProviderInformation.cshtml @@ -2,8 +2,8 @@ @model Bit.Core.AdminConsole.Entities.Provider.Provider
Provider Name
-
@Model.Name
- +
@Model.DisplayName()
+
Provider Type
@(Model.Type.GetDisplayAttribute()?.GetName())
-
\ No newline at end of file + diff --git a/src/Admin/AdminConsole/Views/Providers/AddExistingOrganization.cshtml b/src/Admin/AdminConsole/Views/Providers/AddExistingOrganization.cshtml index 57605a8ec2..44c5c48e3f 100644 --- a/src/Admin/AdminConsole/Views/Providers/AddExistingOrganization.cshtml +++ b/src/Admin/AdminConsole/Views/Providers/AddExistingOrganization.cshtml @@ -45,7 +45,7 @@ @Html.HiddenFor(m => Model.Items[i].Id, new { @readonly = "readonly", autocomplete = "off" }) @Html.CheckBoxFor(m => Model.Items[i].Selected) - @Html.ActionLink(Model.Items[i].Name, "Edit", "Organizations", new { id = Model.Items[i].Id }, new { target = "_blank" }) + @Html.ActionLink(Model.Items[i].DisplayName(), "Edit", "Organizations", new { id = Model.Items[i].Id }, new { target = "_blank" }) @(Model.Items[i].PlanType.GetDisplayAttribute()?.Name ?? Model.Items[i].PlanType.ToString()) } diff --git a/src/Admin/AdminConsole/Views/Providers/Edit.cshtml b/src/Admin/AdminConsole/Views/Providers/Edit.cshtml index ab32b7cad2..cca0a2af28 100644 --- a/src/Admin/AdminConsole/Views/Providers/Edit.cshtml +++ b/src/Admin/AdminConsole/Views/Providers/Edit.cshtml @@ -3,12 +3,12 @@ @model ProviderEditModel @{ - ViewData["Title"] = "Provider: " + Model.Provider.Name; + ViewData["Title"] = "Provider: " + Model.Provider.DisplayName(); var canEdit = AccessControlService.UserHasPermission(Permission.Provider_Edit); } -

Provider @Model.Provider.Name

+

Provider @Model.Provider.DisplayName()

Provider Information

@await Html.PartialAsync("_ViewInformation", Model) @@ -17,12 +17,12 @@

General

Name
-
@Model.Provider.Name
+
@Model.Provider.DisplayName()

Business Information

Business Name
-
@Model.Provider.BusinessName
+
@Model.Provider.DisplayBusinessName()

Billing

diff --git a/src/Admin/AdminConsole/Views/Providers/Index.cshtml b/src/Admin/AdminConsole/Views/Providers/Index.cshtml index 522d06f31e..a8e65f7cfd 100644 --- a/src/Admin/AdminConsole/Views/Providers/Index.cshtml +++ b/src/Admin/AdminConsole/Views/Providers/Index.cshtml @@ -52,7 +52,7 @@ { - @(provider.Name ?? "Pending") + @(!string.IsNullOrEmpty(provider.DisplayName()) ? provider.DisplayName() : "Pending") @provider.Type.GetDisplayAttribute()?.GetShortName() @provider.Status diff --git a/src/Admin/AdminConsole/Views/Providers/Organizations.cshtml b/src/Admin/AdminConsole/Views/Providers/Organizations.cshtml index 258c5f6dcf..2f3455d5ea 100644 --- a/src/Admin/AdminConsole/Views/Providers/Organizations.cshtml +++ b/src/Admin/AdminConsole/Views/Providers/Organizations.cshtml @@ -45,7 +45,7 @@ { - @providerOrganization.OrganizationName + @providerOrganization.DisplayName() @providerOrganization.Status diff --git a/src/Admin/AdminConsole/Views/Providers/View.cshtml b/src/Admin/AdminConsole/Views/Providers/View.cshtml index f1b1b8cd9d..0ae31627fc 100644 --- a/src/Admin/AdminConsole/Views/Providers/View.cshtml +++ b/src/Admin/AdminConsole/Views/Providers/View.cshtml @@ -1,9 +1,9 @@ @model ProviderViewModel @{ - ViewData["Title"] = "Provider: " + Model.Provider.Name; + ViewData["Title"] = "Provider: " + Model.Provider.DisplayName(); } -

Provider @Model.Provider.Name

+

Provider @Model.Provider.DisplayName()

Information

@await Html.PartialAsync("_ViewInformation", Model) diff --git a/src/Admin/AdminConsole/Views/Shared/_OrganizationForm.cshtml b/src/Admin/AdminConsole/Views/Shared/_OrganizationForm.cshtml index fc0834521a..94d6312f56 100644 --- a/src/Admin/AdminConsole/Views/Shared/_OrganizationForm.cshtml +++ b/src/Admin/AdminConsole/Views/Shared/_OrganizationForm.cshtml @@ -28,7 +28,7 @@
- +
@@ -68,7 +68,7 @@
- +
diff --git a/src/Api/AdminConsole/Controllers/OrganizationsController.cs b/src/Api/AdminConsole/Controllers/OrganizationsController.cs index 75296d091f..0bb2e85e97 100644 --- a/src/Api/AdminConsole/Controllers/OrganizationsController.cs +++ b/src/Api/AdminConsole/Controllers/OrganizationsController.cs @@ -303,7 +303,7 @@ public class OrganizationsController : Controller throw new NotFoundException(); } - var updateBilling = !_globalSettings.SelfHosted && (model.BusinessName != organization.BusinessName || + var updateBilling = !_globalSettings.SelfHosted && (model.BusinessName != organization.DisplayBusinessName() || model.BillingEmail != organization.BillingEmail); var hasRequiredPermissions = updateBilling diff --git a/src/Api/AdminConsole/Models/Request/Organizations/OrganizationCreateRequestModel.cs b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationCreateRequestModel.cs index 5cf31460fd..6a3f6b96e2 100644 --- a/src/Api/AdminConsole/Models/Request/Organizations/OrganizationCreateRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationCreateRequestModel.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Models.Business; @@ -9,9 +10,11 @@ namespace Bit.Api.AdminConsole.Models.Request.Organizations; public class OrganizationCreateRequestModel : IValidatableObject { [Required] - [StringLength(50)] + [StringLength(50, ErrorMessage = "The field Name exceeds the maximum length.")] + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string Name { get; set; } - [StringLength(50)] + [StringLength(50, ErrorMessage = "The field Business Name exceeds the maximum length.")] + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string BusinessName { get; set; } [Required] [StringLength(256)] diff --git a/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUpdateRequestModel.cs b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUpdateRequestModel.cs index 10c36cd116..decc04a0db 100644 --- a/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUpdateRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/Organizations/OrganizationUpdateRequestModel.cs @@ -1,16 +1,20 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; using Bit.Core.AdminConsole.Entities; using Bit.Core.Models.Data; using Bit.Core.Settings; +using Bit.Core.Utilities; namespace Bit.Api.AdminConsole.Models.Request.Organizations; public class OrganizationUpdateRequestModel { [Required] - [StringLength(50)] + [StringLength(50, ErrorMessage = "The field Name exceeds the maximum length.")] + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string Name { get; set; } - [StringLength(50)] + [StringLength(50, ErrorMessage = "The field Business Name exceeds the maximum length.")] + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string BusinessName { get; set; } [EmailAddress] [Required] diff --git a/src/Api/AdminConsole/Models/Request/Providers/ProviderSetupRequestModel.cs b/src/Api/AdminConsole/Models/Request/Providers/ProviderSetupRequestModel.cs index e091a55632..f68d3b92a4 100644 --- a/src/Api/AdminConsole/Models/Request/Providers/ProviderSetupRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/Providers/ProviderSetupRequestModel.cs @@ -1,14 +1,18 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; using Bit.Core.AdminConsole.Entities.Provider; +using Bit.Core.Utilities; namespace Bit.Api.AdminConsole.Models.Request.Providers; public class ProviderSetupRequestModel { [Required] - [StringLength(50)] + [StringLength(50, ErrorMessage = "The field Name exceeds the maximum length.")] + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string Name { get; set; } - [StringLength(50)] + [StringLength(50, ErrorMessage = "The field Business Name exceeds the maximum length.")] + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string BusinessName { get; set; } [Required] [StringLength(256)] diff --git a/src/Api/AdminConsole/Models/Request/Providers/ProviderUpdateRequestModel.cs b/src/Api/AdminConsole/Models/Request/Providers/ProviderUpdateRequestModel.cs index 90bdb1e048..e41cb13f4e 100644 --- a/src/Api/AdminConsole/Models/Request/Providers/ProviderUpdateRequestModel.cs +++ b/src/Api/AdminConsole/Models/Request/Providers/ProviderUpdateRequestModel.cs @@ -1,15 +1,19 @@ using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.Settings; +using Bit.Core.Utilities; namespace Bit.Api.AdminConsole.Models.Request.Providers; public class ProviderUpdateRequestModel { [Required] - [StringLength(50)] + [StringLength(50, ErrorMessage = "The field Name exceeds the maximum length.")] + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string Name { get; set; } - [StringLength(50)] + [StringLength(50, ErrorMessage = "The field Business Name exceeds the maximum length.")] + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string BusinessName { get; set; } [EmailAddress] [Required] diff --git a/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs index 6dfbc12cf4..df75a34f69 100644 --- a/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Organizations/OrganizationResponseModel.cs @@ -1,4 +1,5 @@ -using Bit.Api.Models.Response; +using System.Text.Json.Serialization; +using Bit.Api.Models.Response; using Bit.Core.AdminConsole.Entities; using Bit.Core.Enums; using Bit.Core.Models.Api; @@ -60,7 +61,9 @@ public class OrganizationResponseModel : ResponseModel } public Guid Id { get; set; } + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string Name { get; set; } + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string BusinessName { get; set; } public string BusinessAddress1 { get; set; } public string BusinessAddress2 { get; set; } diff --git a/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs index 163b2e27be..8fa41a2f5f 100644 --- a/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/ProfileOrganizationResponseModel.cs @@ -1,4 +1,5 @@ -using Bit.Core.AdminConsole.Enums.Provider; +using System.Text.Json.Serialization; +using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models.Data; using Bit.Core.Enums; @@ -103,6 +104,7 @@ public class ProfileOrganizationResponseModel : ResponseModel } public Guid Id { get; set; } + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string Name { get; set; } public bool UsePolicies { get; set; } public bool UseSso { get; set; } @@ -135,6 +137,7 @@ public class ProfileOrganizationResponseModel : ResponseModel public Guid? UserId { get; set; } public bool HasPublicAndPrivateKeys { get; set; } public Guid? ProviderId { get; set; } + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string ProviderName { get; set; } public ProviderType? ProviderType { get; set; } public string FamilySponsorshipFriendlyName { get; set; } diff --git a/src/Api/AdminConsole/Models/Response/Providers/ProfileProviderResponseModel.cs b/src/Api/AdminConsole/Models/Response/Providers/ProfileProviderResponseModel.cs index 66e0acae0c..9e6927b9e3 100644 --- a/src/Api/AdminConsole/Models/Response/Providers/ProfileProviderResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Providers/ProfileProviderResponseModel.cs @@ -1,4 +1,5 @@ -using Bit.Core.AdminConsole.Enums.Provider; +using System.Text.Json.Serialization; +using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Models.Data.Provider; using Bit.Core.Models.Api; using Bit.Core.Models.Data; @@ -23,6 +24,7 @@ public class ProfileProviderResponseModel : ResponseModel } public Guid Id { get; set; } + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string Name { get; set; } public string Key { get; set; } public ProviderUserStatusType Status { get; set; } diff --git a/src/Api/AdminConsole/Models/Response/Providers/ProviderOrganizationResponseModel.cs b/src/Api/AdminConsole/Models/Response/Providers/ProviderOrganizationResponseModel.cs index da56d49ce8..b8704d7bc6 100644 --- a/src/Api/AdminConsole/Models/Response/Providers/ProviderOrganizationResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Providers/ProviderOrganizationResponseModel.cs @@ -1,6 +1,8 @@ -using Bit.Core.AdminConsole.Entities.Provider; +using System.Text.Json.Serialization; +using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Models.Data.Provider; using Bit.Core.Models.Api; +using Bit.Core.Utilities; namespace Bit.Api.AdminConsole.Models.Response.Providers; @@ -68,5 +70,6 @@ public class ProviderOrganizationOrganizationDetailsResponseModel : ProviderOrga OrganizationName = providerOrganization.OrganizationName; } + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string OrganizationName { get; set; } } diff --git a/src/Api/AdminConsole/Models/Response/Providers/ProviderResponseModel.cs b/src/Api/AdminConsole/Models/Response/Providers/ProviderResponseModel.cs index a7280fd495..6e915444e9 100644 --- a/src/Api/AdminConsole/Models/Response/Providers/ProviderResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/Providers/ProviderResponseModel.cs @@ -1,5 +1,7 @@ -using Bit.Core.AdminConsole.Entities.Provider; +using System.Text.Json.Serialization; +using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.Models.Api; +using Bit.Core.Utilities; namespace Bit.Api.AdminConsole.Models.Response.Providers; @@ -25,6 +27,7 @@ public class ProviderResponseModel : ResponseModel } public Guid Id { get; set; } + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string Name { get; set; } public string BusinessName { get; set; } public string BusinessAddress1 { get; set; } diff --git a/src/Api/Controllers/OrganizationSponsorshipsController.cs b/src/Api/Controllers/OrganizationSponsorshipsController.cs index c24c981014..c01ec101de 100644 --- a/src/Api/Controllers/OrganizationSponsorshipsController.cs +++ b/src/Api/Controllers/OrganizationSponsorshipsController.cs @@ -132,7 +132,7 @@ public class OrganizationSponsorshipsController : Controller } var (syncResponseData, offersToSend) = await _syncSponsorshipsCommand.SyncOrganization(sponsoringOrg, model.ToOrganizationSponsorshipSync().SponsorshipsBatch); - await _sendSponsorshipOfferCommand.BulkSendSponsorshipOfferAsync(sponsoringOrg.Name, offersToSend); + await _sendSponsorshipOfferCommand.BulkSendSponsorshipOfferAsync(sponsoringOrg.DisplayName(), offersToSend); return new OrganizationSponsorshipSyncResponseModel(syncResponseData); } diff --git a/src/Api/Jobs/SelfHostedSponsorshipSyncJob.cs b/src/Api/Jobs/SelfHostedSponsorshipSyncJob.cs index d217598242..b4d9d75aa0 100644 --- a/src/Api/Jobs/SelfHostedSponsorshipSyncJob.cs +++ b/src/Api/Jobs/SelfHostedSponsorshipSyncJob.cs @@ -58,7 +58,7 @@ public class SelfHostedSponsorshipSyncJob : BaseJob } catch (Exception ex) { - _logger.LogError(ex, $"Sponsorship sync for organization {org.Name} Failed"); + _logger.LogError(ex, "Sponsorship sync for organization {OrganizationName} Failed", org.DisplayName()); } } } diff --git a/src/Billing/Controllers/FreshsalesController.cs b/src/Billing/Controllers/FreshsalesController.cs index 545f285e08..7ce7565fca 100644 --- a/src/Billing/Controllers/FreshsalesController.cs +++ b/src/Billing/Controllers/FreshsalesController.cs @@ -95,7 +95,7 @@ public class FreshsalesController : Controller foreach (var org in orgs) { - noteItems.Add($"Org, {org.Name}: {_globalSettings.BaseServiceUri.Admin}/organizations/edit/{org.Id}"); + noteItems.Add($"Org, {org.DisplayName()}: {_globalSettings.BaseServiceUri.Admin}/organizations/edit/{org.Id}"); if (TryGetPlanName(org.PlanType, out var planName)) { newTags.Add($"Org: {planName}"); diff --git a/src/Core/AdminConsole/Entities/Organization.cs b/src/Core/AdminConsole/Entities/Organization.cs index cd6f317bab..fc8e515bd9 100644 --- a/src/Core/AdminConsole/Entities/Organization.cs +++ b/src/Core/AdminConsole/Entities/Organization.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Net; using System.Text.Json; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models; @@ -17,8 +18,14 @@ public class Organization : ITableObject, IStorableSubscriber, IRevisable, public Guid Id { get; set; } [MaxLength(50)] public string Identifier { get; set; } + /// + /// This value is HTML encoded. For display purposes use the method DisplayName() instead. + /// [MaxLength(50)] public string Name { get; set; } + /// + /// This value is HTML encoded. For display purposes use the method DisplayBusinessName() instead. + /// [MaxLength(50)] public string BusinessName { get; set; } [MaxLength(50)] @@ -104,6 +111,22 @@ public class Organization : ITableObject, IStorableSubscriber, IRevisable, } } + /// + /// Returns the name of the organization, HTML decoded ready for display. + /// + public string DisplayName() + { + return WebUtility.HtmlDecode(Name); + } + + /// + /// Returns the business name of the organization, HTML decoded ready for display. + /// + public string DisplayBusinessName() + { + return WebUtility.HtmlDecode(BusinessName); + } + public string BillingEmailAddress() { return BillingEmail?.ToLowerInvariant()?.Trim(); @@ -111,12 +134,12 @@ public class Organization : ITableObject, IStorableSubscriber, IRevisable, public string BillingName() { - return BusinessName; + return DisplayBusinessName(); } public string SubscriberName() { - return Name; + return DisplayName(); } public string BraintreeCustomerIdPrefix() diff --git a/src/Core/AdminConsole/Entities/Provider/Provider.cs b/src/Core/AdminConsole/Entities/Provider/Provider.cs index 7aeb422cf3..2c98cc9aba 100644 --- a/src/Core/AdminConsole/Entities/Provider/Provider.cs +++ b/src/Core/AdminConsole/Entities/Provider/Provider.cs @@ -1,4 +1,5 @@ -using Bit.Core.AdminConsole.Enums.Provider; +using System.Net; +using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.Entities; using Bit.Core.Utilities; @@ -7,7 +8,13 @@ namespace Bit.Core.AdminConsole.Entities.Provider; public class Provider : ITableObject { public Guid Id { get; set; } + /// + /// This value is HTML encoded. For display purposes use the method DisplayName() instead. + /// public string Name { get; set; } + /// + /// This value is HTML encoded. For display purposes use the method DisplayBusinessName() instead. + /// public string BusinessName { get; set; } public string BusinessAddress1 { get; set; } public string BusinessAddress2 { get; set; } @@ -30,4 +37,20 @@ public class Provider : ITableObject Id = CoreHelpers.GenerateComb(); } } + + /// + /// Returns the name of the provider, HTML decoded ready for display. + /// + public string DisplayName() + { + return WebUtility.HtmlDecode(Name); + } + + /// + /// Returns the business name of the provider, HTML decoded ready for display. + /// + public string DisplayBusinessName() + { + return WebUtility.HtmlDecode(BusinessName); + } } diff --git a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs index e9d5a4bd50..383505af44 100644 --- a/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Organizations/OrganizationUsers/OrganizationUserOrganizationDetails.cs @@ -1,4 +1,6 @@ -using Bit.Core.AdminConsole.Enums.Provider; +using System.Text.Json.Serialization; +using Bit.Core.AdminConsole.Enums.Provider; +using Bit.Core.Utilities; namespace Bit.Core.Models.Data.Organizations.OrganizationUsers; @@ -6,6 +8,7 @@ public class OrganizationUserOrganizationDetails { public Guid OrganizationId { get; set; } public Guid? UserId { get; set; } + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string Name { get; set; } public bool UsePolicies { get; set; } public bool UseSso { get; set; } @@ -37,6 +40,7 @@ public class OrganizationUserOrganizationDetails public string PublicKey { get; set; } public string PrivateKey { get; set; } public Guid? ProviderId { get; set; } + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string ProviderName { get; set; } public ProviderType? ProviderType { get; set; } public string FamilySponsorshipFriendlyName { get; set; } diff --git a/src/Core/AdminConsole/Models/Data/Provider/ProviderOrganizationOrganizationDetails.cs b/src/Core/AdminConsole/Models/Data/Provider/ProviderOrganizationOrganizationDetails.cs index dc6d80b880..d1baac001d 100644 --- a/src/Core/AdminConsole/Models/Data/Provider/ProviderOrganizationOrganizationDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Provider/ProviderOrganizationOrganizationDetails.cs @@ -1,4 +1,7 @@ -using Bit.Core.Enums; +using System.Net; +using System.Text.Json.Serialization; +using Bit.Core.Enums; +using Bit.Core.Utilities; namespace Bit.Core.AdminConsole.Models.Data.Provider; @@ -7,6 +10,10 @@ public class ProviderOrganizationOrganizationDetails public Guid Id { get; set; } public Guid ProviderId { get; set; } public Guid OrganizationId { get; set; } + /// + /// This value is HTML encoded. For display purposes use the method DisplayName() instead. + /// + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string OrganizationName { get; set; } public string Key { get; set; } public string Settings { get; set; } @@ -16,4 +23,12 @@ public class ProviderOrganizationOrganizationDetails public int? Seats { get; set; } public string Plan { get; set; } public OrganizationStatusType Status { get; set; } + + /// + /// Returns the name of the organization, HTML decoded ready for display. + /// + public string DisplayName() + { + return WebUtility.HtmlDecode(OrganizationName); + } } diff --git a/src/Core/AdminConsole/Models/Data/Provider/ProviderOrganizationProviderDetails.cs b/src/Core/AdminConsole/Models/Data/Provider/ProviderOrganizationProviderDetails.cs index e0a6faabbf..629e0bae53 100644 --- a/src/Core/AdminConsole/Models/Data/Provider/ProviderOrganizationProviderDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Provider/ProviderOrganizationProviderDetails.cs @@ -1,4 +1,6 @@ -using Bit.Core.AdminConsole.Enums.Provider; +using System.Text.Json.Serialization; +using Bit.Core.AdminConsole.Enums.Provider; +using Bit.Core.Utilities; namespace Bit.Core.AdminConsole.Models.Data.Provider; @@ -7,6 +9,7 @@ public class ProviderOrganizationProviderDetails public Guid Id { get; set; } public Guid ProviderId { get; set; } public Guid OrganizationId { get; set; } + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string ProviderName { get; set; } public ProviderType ProviderType { get; set; } } diff --git a/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs b/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs index e692179498..5acd3eec2d 100644 --- a/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Provider/ProviderUserOrganizationDetails.cs @@ -1,4 +1,6 @@ -using Bit.Core.AdminConsole.Enums.Provider; +using System.Text.Json.Serialization; +using Bit.Core.AdminConsole.Enums.Provider; +using Bit.Core.Utilities; namespace Bit.Core.AdminConsole.Models.Data.Provider; @@ -6,6 +8,7 @@ public class ProviderUserOrganizationDetails { public Guid OrganizationId { get; set; } public Guid? UserId { get; set; } + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string Name { get; set; } public bool UsePolicies { get; set; } public bool UseSso { get; set; } @@ -33,6 +36,7 @@ public class ProviderUserOrganizationDetails public string PrivateKey { get; set; } public Guid? ProviderId { get; set; } public Guid? ProviderUserId { get; set; } + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string ProviderName { get; set; } public Core.Enums.PlanType PlanType { get; set; } public bool LimitCollectionCreationDeletion { get; set; } diff --git a/src/Core/AdminConsole/Models/Data/Provider/ProviderUserProviderDetails.cs b/src/Core/AdminConsole/Models/Data/Provider/ProviderUserProviderDetails.cs index 2b7d240a5c..23f4e1d57a 100644 --- a/src/Core/AdminConsole/Models/Data/Provider/ProviderUserProviderDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Provider/ProviderUserProviderDetails.cs @@ -1,4 +1,6 @@ -using Bit.Core.AdminConsole.Enums.Provider; +using System.Text.Json.Serialization; +using Bit.Core.AdminConsole.Enums.Provider; +using Bit.Core.Utilities; namespace Bit.Core.AdminConsole.Models.Data.Provider; @@ -6,6 +8,7 @@ public class ProviderUserProviderDetails { public Guid ProviderId { get; set; } public Guid? UserId { get; set; } + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string Name { get; set; } public string Key { get; set; } public ProviderUserStatusType Status { get; set; } diff --git a/src/Core/AdminConsole/Models/Data/Provider/ProviderUserUserDetails.cs b/src/Core/AdminConsole/Models/Data/Provider/ProviderUserUserDetails.cs index 94aa3fa0f3..d42437a26e 100644 --- a/src/Core/AdminConsole/Models/Data/Provider/ProviderUserUserDetails.cs +++ b/src/Core/AdminConsole/Models/Data/Provider/ProviderUserUserDetails.cs @@ -1,4 +1,6 @@ -using Bit.Core.AdminConsole.Enums.Provider; +using System.Text.Json.Serialization; +using Bit.Core.AdminConsole.Enums.Provider; +using Bit.Core.Utilities; namespace Bit.Core.AdminConsole.Models.Data.Provider; @@ -7,6 +9,7 @@ public class ProviderUserUserDetails public Guid Id { get; set; } public Guid ProviderId { get; set; } public Guid? UserId { get; set; } + [JsonConverter(typeof(HtmlEncodingStringConverter))] public string Name { get; set; } public string Email { get; set; } public ProviderUserStatusType Status { get; set; } diff --git a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs index ba46cda8ce..df09a81d8c 100644 --- a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs +++ b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs @@ -828,7 +828,7 @@ public class OrganizationService : IOrganizationService await customerService.UpdateAsync(organization.GatewayCustomerId, new CustomerUpdateOptions { Email = organization.BillingEmail, - Description = organization.BusinessName + Description = organization.DisplayBusinessName() }); } } @@ -1285,7 +1285,7 @@ public class OrganizationService : IOrganizationService orgUser.Email = null; await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Confirmed); - await _mailService.SendOrganizationConfirmedEmailAsync(organization.Name, user.Email); + await _mailService.SendOrganizationConfirmedEmailAsync(organization.DisplayName(), user.Email); await DeleteAndPushUserRegistrationAsync(organizationId, user.Id); succeededUsers.Add(orgUser); result.Add(Tuple.Create(orgUser, "")); diff --git a/src/Core/AdminConsole/Services/Implementations/PolicyService.cs b/src/Core/AdminConsole/Services/Implementations/PolicyService.cs index 4726de0160..246b4d56dd 100644 --- a/src/Core/AdminConsole/Services/Implementations/PolicyService.cs +++ b/src/Core/AdminConsole/Services/Implementations/PolicyService.cs @@ -131,7 +131,7 @@ public class PolicyService : IPolicyService await organizationService.DeleteUserAsync(policy.OrganizationId, orgUser.Id, savingUserId); await _mailService.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync( - org.Name, orgUser.Email); + org.DisplayName(), orgUser.Email); } } break; @@ -147,7 +147,7 @@ public class PolicyService : IPolicyService await organizationService.DeleteUserAsync(policy.OrganizationId, orgUser.Id, savingUserId); await _mailService.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync( - org.Name, orgUser.Email); + org.DisplayName(), orgUser.Email); } } break; diff --git a/src/Core/Models/Mail/OrganizationInvitesInfo.cs b/src/Core/Models/Mail/OrganizationInvitesInfo.cs index 10602bac61..e726b929a6 100644 --- a/src/Core/Models/Mail/OrganizationInvitesInfo.cs +++ b/src/Core/Models/Mail/OrganizationInvitesInfo.cs @@ -15,7 +15,7 @@ public class OrganizationInvitesInfo bool initOrganization = false ) { - OrganizationName = org.Name; + OrganizationName = org.DisplayName(); OrgSsoIdentifier = org.Identifier; IsFreeOrg = org.PlanType == PlanType.Free; diff --git a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/SendSponsorshipOfferCommand.cs b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/SendSponsorshipOfferCommand.cs index 0af62b10ff..e070b263a3 100644 --- a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/SendSponsorshipOfferCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/SendSponsorshipOfferCommand.cs @@ -65,6 +65,6 @@ public class SendSponsorshipOfferCommand : ISendSponsorshipOfferCommand throw new BadRequestException("Cannot find an outstanding sponsorship offer for this organization."); } - await SendSponsorshipOfferAsync(sponsorship, sponsoringOrg.Name); + await SendSponsorshipOfferAsync(sponsorship, sponsoringOrg.DisplayName()); } } diff --git a/src/Core/Services/Implementations/HandlebarsMailService.cs b/src/Core/Services/Implementations/HandlebarsMailService.cs index aec4937f9e..19407af0d1 100644 --- a/src/Core/Services/Implementations/HandlebarsMailService.cs +++ b/src/Core/Services/Implementations/HandlebarsMailService.cs @@ -145,7 +145,7 @@ public class HandlebarsMailService : IMailService public async Task SendOrganizationAutoscaledEmailAsync(Organization organization, int initialSeatCount, IEnumerable ownerEmails) { - var message = CreateDefaultMessage($"{organization.Name} Seat Count Has Increased", ownerEmails); + var message = CreateDefaultMessage($"{organization.DisplayName()} Seat Count Has Increased", ownerEmails); var model = new OrganizationSeatsAutoscaledViewModel { OrganizationId = organization.Id, @@ -160,7 +160,7 @@ public class HandlebarsMailService : IMailService public async Task SendOrganizationMaxSeatLimitReachedEmailAsync(Organization organization, int maxSeatCount, IEnumerable ownerEmails) { - var message = CreateDefaultMessage($"{organization.Name} Seat Limit Reached", ownerEmails); + var message = CreateDefaultMessage($"{organization.DisplayName()} Seat Limit Reached", ownerEmails); var model = new OrganizationSeatsMaxReachedViewModel { OrganizationId = organization.Id, @@ -179,7 +179,7 @@ public class HandlebarsMailService : IMailService var model = new OrganizationUserAcceptedViewModel { OrganizationId = organization.Id, - OrganizationName = CoreHelpers.SanitizeForEmail(organization.Name, false), + OrganizationName = CoreHelpers.SanitizeForEmail(organization.DisplayName(), false), UserIdentifier = userIdentifier, WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash, SiteName = _globalSettings.SiteName @@ -933,7 +933,7 @@ public class HandlebarsMailService : IMailService public async Task SendSecretsManagerMaxSeatLimitReachedEmailAsync(Organization organization, int maxSeatCount, IEnumerable ownerEmails) { - var message = CreateDefaultMessage($"{organization.Name} Secrets Manager Seat Limit Reached", ownerEmails); + var message = CreateDefaultMessage($"{organization.DisplayName()} Secrets Manager Seat Limit Reached", ownerEmails); var model = new OrganizationSeatsMaxReachedViewModel { OrganizationId = organization.Id, @@ -948,7 +948,7 @@ public class HandlebarsMailService : IMailService public async Task SendSecretsManagerMaxServiceAccountLimitReachedEmailAsync(Organization organization, int maxSeatCount, IEnumerable ownerEmails) { - var message = CreateDefaultMessage($"{organization.Name} Secrets Manager Service Accounts Limit Reached", ownerEmails); + var message = CreateDefaultMessage($"{organization.DisplayName()} Secrets Manager Service Accounts Limit Reached", ownerEmails); var model = new OrganizationServiceAccountsMaxReachedViewModel { OrganizationId = organization.Id, diff --git a/src/Core/Services/Implementations/LicensingService.cs b/src/Core/Services/Implementations/LicensingService.cs index f84e68c256..85b8f31200 100644 --- a/src/Core/Services/Implementations/LicensingService.cs +++ b/src/Core/Services/Implementations/LicensingService.cs @@ -132,13 +132,13 @@ public class LicensingService : ILicensingService { _logger.LogInformation(Constants.BypassFiltersEventId, null, "Organization {0} ({1}) has an invalid license and is being disabled. Reason: {2}", - org.Id, org.Name, reason); + org.Id, org.DisplayName(), reason); org.Enabled = false; org.ExpirationDate = license?.Expires ?? DateTime.UtcNow; org.RevisionDate = DateTime.UtcNow; await _organizationRepository.ReplaceAsync(org); - await _mailService.SendLicenseExpiredAsync(new List { org.BillingEmail }, org.Name); + await _mailService.SendLicenseExpiredAsync(new List { org.BillingEmail }, org.DisplayName()); } public async Task ValidateUsersAsync() diff --git a/src/Core/Services/Implementations/StripePaymentService.cs b/src/Core/Services/Implementations/StripePaymentService.cs index 2aa715eefb..d1ce2d784a 100644 --- a/src/Core/Services/Implementations/StripePaymentService.cs +++ b/src/Core/Services/Implementations/StripePaymentService.cs @@ -131,7 +131,7 @@ public class StripePaymentService : IPaymentService { customer = await _stripeAdapter.CustomerCreateAsync(new Stripe.CustomerCreateOptions { - Description = org.BusinessName, + Description = org.DisplayBusinessName(), Email = org.BillingEmail, Source = stipeCustomerSourceToken, PaymentMethod = stipeCustomerPaymentMethodId, diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index a40d4bf302..f1583d98a7 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -797,7 +797,7 @@ public class UserService : UserManager, IUserService, IDisposable user.ForcePasswordReset = true; await _userRepository.ReplaceAsync(user); - await _mailService.SendAdminResetPasswordEmailAsync(user.Email, user.Name, org.Name); + await _mailService.SendAdminResetPasswordEmailAsync(user.Email, user.Name, org.DisplayName()); await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_AdminResetPassword); await _pushService.PushLogOutAsync(user.Id); @@ -1391,7 +1391,7 @@ public class UserService : UserManager, IUserService, IDisposable await organizationService.DeleteUserAsync(p.OrganizationId, user.Id); var organization = await _organizationRepository.GetByIdAsync(p.OrganizationId); await _mailService.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync( - organization.Name, user.Email); + organization.DisplayName(), user.Email); }).ToArray(); await Task.WhenAll(removeOrgUserTasks); diff --git a/src/Core/Utilities/JsonHelpers.cs b/src/Core/Utilities/JsonHelpers.cs index 77ab866676..3f06794b7c 100644 --- a/src/Core/Utilities/JsonHelpers.cs +++ b/src/Core/Utilities/JsonHelpers.cs @@ -1,4 +1,5 @@ using System.Globalization; +using System.Net; using System.Text.Json; using System.Text.Json.Serialization; using NS = Newtonsoft.Json; @@ -192,3 +193,33 @@ public class PermissiveStringEnumerableConverter : JsonConverter +/// Encodes incoming strings using HTML encoding +/// and decodes outgoing strings using HTML decoding. +/// +public class HtmlEncodingStringConverter : JsonConverter +{ + public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + var originalValue = reader.GetString(); + return WebUtility.HtmlEncode(originalValue); + } + return reader.GetString(); + } + + public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + { + if (!string.IsNullOrEmpty(value)) + { + var encodedValue = WebUtility.HtmlDecode(value); + writer.WriteStringValue(encodedValue); + } + else + { + writer.WriteNullValue(); + } + } +} diff --git a/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs b/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs index c3fe9e694e..f2e324c6d6 100644 --- a/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs +++ b/test/Core.Test/AdminConsole/Services/OrganizationServiceTests.cs @@ -1744,7 +1744,7 @@ OrganizationUserInvite invite, SutProvider sutProvider) await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, userService); await sutProvider.GetDependency().Received(1).LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Confirmed); - await sutProvider.GetDependency().Received(1).SendOrganizationConfirmedEmailAsync(org.Name, user.Email); + await sutProvider.GetDependency().Received(1).SendOrganizationConfirmedEmailAsync(org.DisplayName(), user.Email); await organizationUserRepository.Received(1).ReplaceManyAsync(Arg.Is>(users => users.Contains(orgUser) && users.Count == 1)); } diff --git a/test/Core.Test/AdminConsole/Services/PolicyServiceTests.cs b/test/Core.Test/AdminConsole/Services/PolicyServiceTests.cs index 24ca0f76d9..2ae02376b5 100644 --- a/test/Core.Test/AdminConsole/Services/PolicyServiceTests.cs +++ b/test/Core.Test/AdminConsole/Services/PolicyServiceTests.cs @@ -329,7 +329,7 @@ public class PolicyServiceTests .DeleteUserAsync(policy.OrganizationId, orgUserDetail.Id, savingUserId); await sutProvider.GetDependency().Received() - .SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(org.Name, orgUserDetail.Email); + .SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(org.DisplayName(), orgUserDetail.Email); await sutProvider.GetDependency().Received() .LogPolicyEventAsync(policy, EventType.Policy_Updated); diff --git a/test/Core.Test/Utilities/HtmlEncodingStringConverterTests.cs b/test/Core.Test/Utilities/HtmlEncodingStringConverterTests.cs new file mode 100644 index 0000000000..88d3ddd49e --- /dev/null +++ b/test/Core.Test/Utilities/HtmlEncodingStringConverterTests.cs @@ -0,0 +1,89 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Bit.Core.Utilities; +using Xunit; + +namespace Bit.Core.Test.Utilities; + +public class HtmlEncodingStringConverterTests +{ + [Fact] + public void Serialize_WhenEncodedValueIsNotNull_SerializesHtmlEncodedString() + { + // Arrange + var obj = new HtmlEncodedString + { + EncodedValue = "This is <b>bold</b>", + NonEncodedValue = "This is bold" + }; + const string expectedJsonString = "{\"EncodedValue\":\"This is bold\",\"NonEncodedValue\":\"This is bold\"}"; + + // This is necessary to prevent the serializer from double encoding the string + var serializerOptions = new JsonSerializerOptions + { + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + }; + + // Act + var jsonString = JsonSerializer.Serialize(obj, serializerOptions); + + // Assert + Assert.Equal(expectedJsonString, jsonString); + } + + [Fact] + public void Serialize_WhenEncodedValueIsNull_SerializesNull() + { + // Arrange + var obj = new HtmlEncodedString + { + EncodedValue = null, + NonEncodedValue = null + }; + const string expectedJsonString = "{\"EncodedValue\":null,\"NonEncodedValue\":null}"; + + // Act + var jsonString = JsonSerializer.Serialize(obj); + + // Assert + Assert.Equal(expectedJsonString, jsonString); + } + + [Fact] + public void Deserialize_WhenJsonContainsHtmlEncodedString_ReturnsDecodedString() + { + // Arrange + const string json = "{\"EncodedValue\":\"This is bold\",\"NonEncodedValue\":\"This is bold\"}"; + const string expectedEncodedValue = "This is <b>bold</b>"; + const string expectedNonEncodedValue = "This is bold"; + + // Act + var obj = JsonSerializer.Deserialize(json); + + // Assert + Assert.Equal(expectedEncodedValue, obj.EncodedValue); + Assert.Equal(expectedNonEncodedValue, obj.NonEncodedValue); + } + + [Fact] + public void Deserialize_WhenJsonContainsNull_ReturnsNull() + { + // Arrange + const string json = "{\"EncodedValue\":null,\"NonEncodedValue\":null}"; + + // Act + var obj = JsonSerializer.Deserialize(json); + + // Assert + Assert.Null(obj.EncodedValue); + Assert.Null(obj.NonEncodedValue); + } +} + +public class HtmlEncodedString +{ + [JsonConverter(typeof(HtmlEncodingStringConverter))] + public string EncodedValue { get; set; } + + public string NonEncodedValue { get; set; } +}