mirror of
https://github.com/bitwarden/server.git
synced 2025-07-07 10:55:43 -05:00
[AC-1637] Sanitize Business and Organization Names from html script injection prior to storing in db (#3302)
* [AC-1637] Added HtmlEncodingStringConverter to encode/decode special chars on JSON serialization/deserialization
* [AC-1637] Added unit tests for HtmlEncodingStringConverter
* [AC-1637] Moved expected values on unit tests to the arrange phase
* [AC-1637] Added HtmlEncodingStringConverter to properties that are for input/output of Org Name and Business name
* [AC-1637] Modified views in Admin project to decode values to display
* [AC-1637] Replaced Html.Raw with HttpUtility.HtmlDecode
* [AC-1637] Added JsonConverter to Provider DTOs
* [AC-1637] Modified HandlebarsMailService to decode organization name before sending emails
* Revert "[AC-1637] Added JsonConverter to Provider DTOs"
This reverts commit 94d507cf93
.
* [AC-1637] Fixed Admin panel organization search
* [AC-1637] Sanitizing Organization name and business name on creation in Admin panel
* [AC-1637] Sanitizing organization name and business name on creation by a provider
* [AC-1637] Sanitizing provider name on creation and on viewing in admin panel
* [AC-1637] Added sanitization to more places where Org name is used
* [AC-1637] Swapped using HttpUtility for WebUtility since the later is part of the dotnet framework
* [AC-1637] Updated error messages
* [AC-1637] Decoding on Admin panel add existing organization
* [AC-1637] Fix HTML decoding issues
* [AC-1637] Refactor HTML decoding in View and Model classes on Admin panel
* [AC-1637] Refactor provider name and business name usages to use methods that output decoded values
* [AC-1637] Fixed typo
* [AC-1637] Renamed Provider methods to retrieve Decoded Name and BusinessName
* [AC-1637] Renamed Organization methods to retrieve Decoded Name and BusinessName
* [AC-1637] Update the display name method in the `ProviderOrganizationOrganizationDetails` class to `DisplayName()`
This commit is contained in:
@ -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<Guid>, IStorableSubscriber, IRevisable,
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(50)]
|
||||
public string Identifier { get; set; }
|
||||
/// <summary>
|
||||
/// This value is HTML encoded. For display purposes use the method DisplayName() instead.
|
||||
/// </summary>
|
||||
[MaxLength(50)]
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// This value is HTML encoded. For display purposes use the method DisplayBusinessName() instead.
|
||||
/// </summary>
|
||||
[MaxLength(50)]
|
||||
public string BusinessName { get; set; }
|
||||
[MaxLength(50)]
|
||||
@ -104,6 +111,22 @@ public class Organization : ITableObject<Guid>, IStorableSubscriber, IRevisable,
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the organization, HTML decoded ready for display.
|
||||
/// </summary>
|
||||
public string DisplayName()
|
||||
{
|
||||
return WebUtility.HtmlDecode(Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the business name of the organization, HTML decoded ready for display.
|
||||
/// </summary>
|
||||
public string DisplayBusinessName()
|
||||
{
|
||||
return WebUtility.HtmlDecode(BusinessName);
|
||||
}
|
||||
|
||||
public string BillingEmailAddress()
|
||||
{
|
||||
return BillingEmail?.ToLowerInvariant()?.Trim();
|
||||
@ -111,12 +134,12 @@ public class Organization : ITableObject<Guid>, IStorableSubscriber, IRevisable,
|
||||
|
||||
public string BillingName()
|
||||
{
|
||||
return BusinessName;
|
||||
return DisplayBusinessName();
|
||||
}
|
||||
|
||||
public string SubscriberName()
|
||||
{
|
||||
return Name;
|
||||
return DisplayName();
|
||||
}
|
||||
|
||||
public string BraintreeCustomerIdPrefix()
|
||||
|
@ -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<Guid>
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
/// <summary>
|
||||
/// This value is HTML encoded. For display purposes use the method DisplayName() instead.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// This value is HTML encoded. For display purposes use the method DisplayBusinessName() instead.
|
||||
/// </summary>
|
||||
public string BusinessName { get; set; }
|
||||
public string BusinessAddress1 { get; set; }
|
||||
public string BusinessAddress2 { get; set; }
|
||||
@ -30,4 +37,20 @@ public class Provider : ITableObject<Guid>
|
||||
Id = CoreHelpers.GenerateComb();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the provider, HTML decoded ready for display.
|
||||
/// </summary>
|
||||
public string DisplayName()
|
||||
{
|
||||
return WebUtility.HtmlDecode(Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the business name of the provider, HTML decoded ready for display.
|
||||
/// </summary>
|
||||
public string DisplayBusinessName()
|
||||
{
|
||||
return WebUtility.HtmlDecode(BusinessName);
|
||||
}
|
||||
}
|
||||
|
@ -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; }
|
||||
|
@ -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; }
|
||||
/// <summary>
|
||||
/// This value is HTML encoded. For display purposes use the method DisplayName() instead.
|
||||
/// </summary>
|
||||
[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; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the organization, HTML decoded ready for display.
|
||||
/// </summary>
|
||||
public string DisplayName()
|
||||
{
|
||||
return WebUtility.HtmlDecode(OrganizationName);
|
||||
}
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -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; }
|
||||
|
@ -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; }
|
||||
|
@ -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; }
|
||||
|
@ -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, ""));
|
||||
|
@ -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;
|
||||
|
@ -15,7 +15,7 @@ public class OrganizationInvitesInfo
|
||||
bool initOrganization = false
|
||||
)
|
||||
{
|
||||
OrganizationName = org.Name;
|
||||
OrganizationName = org.DisplayName();
|
||||
OrgSsoIdentifier = org.Identifier;
|
||||
|
||||
IsFreeOrg = org.PlanType == PlanType.Free;
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ public class HandlebarsMailService : IMailService
|
||||
|
||||
public async Task SendOrganizationAutoscaledEmailAsync(Organization organization, int initialSeatCount, IEnumerable<string> 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<string> 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<string> 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<string> 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,
|
||||
|
@ -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<string> { org.BillingEmail }, org.Name);
|
||||
await _mailService.SendLicenseExpiredAsync(new List<string> { org.BillingEmail }, org.DisplayName());
|
||||
}
|
||||
|
||||
public async Task ValidateUsersAsync()
|
||||
|
@ -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,
|
||||
|
@ -797,7 +797,7 @@ public class UserService : UserManager<User>, 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<User>, 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);
|
||||
|
@ -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<IEnumerable<str
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes incoming strings using HTML encoding
|
||||
/// and decodes outgoing strings using HTML decoding.
|
||||
/// </summary>
|
||||
public class HtmlEncodingStringConverter : JsonConverter<string>
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user