Browse Source

Cms: Don't allow external URLs

pull/16174/head
Engincan VESKE 4 years ago
parent
commit
51ca07033c
  1. 5
      modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs
  2. 2
      modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Index.cshtml
  3. 6
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Comments/CmsKitCommentOptions.cs
  4. 3
      modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json
  5. 2
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentInput.cs
  6. 4
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentWithParametersInput.cs
  7. 2
      modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/UpdateCommentInput.cs
  8. 54
      modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs
  9. 2
      modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebAutoMapperProfile.cs
  10. 4
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/CmsKitPublicCommentsController.cs
  11. 6
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/CommentingViewComponent.cs
  12. 2
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml
  13. 6
      modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.js

5
modules/cms-kit/host/Volo.CmsKit.Web.Unified/CmsKitWebUnifiedModule.cs

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.IO; using System.IO;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -168,6 +169,10 @@ public class CmsKitWebUnifiedModule : AbpModule
{ {
options.EntityTypes.Add(new CommentEntityTypeDefinition("quote")); options.EntityTypes.Add(new CommentEntityTypeDefinition("quote"));
options.IsRecaptchaEnabled = true; options.IsRecaptchaEnabled = true;
options.AllowedExternalUrls = new List<string>
{
"https://abp.io/"
};
}); });
Configure<CmsKitMediaOptions>(options => Configure<CmsKitMediaOptions>(options =>

2
modules/cms-kit/host/Volo.CmsKit.Web.Unified/Pages/Index.cshtml

@ -90,7 +90,7 @@
<abp-column size-md="_12"> <abp-column size-md="_12">
@if (GlobalFeatureManager.Instance.IsEnabled<CommentsFeature>()) @if (GlobalFeatureManager.Instance.IsEnabled<CommentsFeature>())
{ {
@await Component.InvokeAsync(typeof(CommentingViewComponent), new {entityType = "quote", entityId = "2"}) @await Component.InvokeAsync(typeof(CommentingViewComponent), new {entityType = "quote", entityId = "2", allowExternalUrls = false})
} }
</abp-column> </abp-column>
</abp-row> </abp-row>

6
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Comments/CmsKitCommentOptions.cs

@ -13,4 +13,10 @@ public class CmsKitCommentOptions
/// Default: false /// Default: false
/// </summary> /// </summary>
public bool IsRecaptchaEnabled { get; set; } public bool IsRecaptchaEnabled { get; set; }
/// <summary>
/// Indicates the allowed external URLs, which can be included in a comment.
/// If it's not specified, all external URLs will be allowed.
/// </summary>
public List<string> AllowedExternalUrls { get; set; } = new();
} }

3
modules/cms-kit/src/Volo.CmsKit.Domain.Shared/Volo/CmsKit/Localization/Resources/en.json

@ -216,6 +216,7 @@
"CaptchaCode": "Captcha code", "CaptchaCode": "Captcha code",
"CommentTextRequired": "Comment is required", "CommentTextRequired": "Comment is required",
"CaptchaCodeErrorMessage" : "The answer you entered for the CAPTCHA was not correct. Please try again", "CaptchaCodeErrorMessage" : "The answer you entered for the CAPTCHA was not correct. Please try again",
"CaptchaCodeMissingMessage": "The captcha code is missing!" "CaptchaCodeMissingMessage": "The captcha code is missing!",
"UnAllowedExternalUrlMessage": "You included an unallowed external URL. Please try again without the external URL."
} }
} }

2
modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentInput.cs

@ -17,4 +17,6 @@ public class CreateCommentInput
public Guid? CaptchaToken { get; set; } public Guid? CaptchaToken { get; set; }
public int CaptchaAnswer { get; set; } public int CaptchaAnswer { get; set; }
public bool AllowExternalUrls { get; set; } = true;
} }

4
modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentWithParameteresInput.cs → modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/CreateCommentWithParametersInput.cs

@ -6,7 +6,7 @@ using Volo.CmsKit.Comments;
namespace Volo.CmsKit.Public.Comments; namespace Volo.CmsKit.Public.Comments;
[Serializable] [Serializable]
public class CreateCommentWithParameteresInput public class CreateCommentWithParametersInput
{ {
[Required] [Required]
[DynamicStringLength(typeof(CommentConsts), nameof(CommentConsts.MaxTextLength))] [DynamicStringLength(typeof(CommentConsts), nameof(CommentConsts.MaxTextLength))]
@ -18,6 +18,8 @@ public class CreateCommentWithParameteresInput
[Required] [Required]
public string EntityId { get; set; } public string EntityId { get; set; }
public bool AllowExternalUrls { get; set; } = true;
public Guid? RepliedCommentId { get; set; } public Guid? RepliedCommentId { get; set; }
public Guid? CaptchaToken { get; set; } public Guid? CaptchaToken { get; set; }

2
modules/cms-kit/src/Volo.CmsKit.Public.Application.Contracts/Volo/CmsKit/Public/Comments/UpdateCommentInput.cs

@ -14,4 +14,6 @@ public class UpdateCommentInput : IHasConcurrencyStamp
public string Text { get; set; } public string Text { get; set; }
public string ConcurrencyStamp { get; set; } public string ConcurrencyStamp { get; set; }
public bool AllowExternalUrls { get; set; } = true;
} }

54
modules/cms-kit/src/Volo.CmsKit.Public.Application/Volo/CmsKit/Public/Comments/CommentPublicAppService.cs

@ -1,9 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using Volo.Abp;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Authorization; using Volo.Abp.Authorization;
using Volo.Abp.Data; using Volo.Abp.Data;
@ -23,24 +25,27 @@ namespace Volo.CmsKit.Public.Comments;
[RequiresGlobalFeature(typeof(CommentsFeature))] [RequiresGlobalFeature(typeof(CommentsFeature))]
public class CommentPublicAppService : CmsKitPublicAppServiceBase, ICommentPublicAppService public class CommentPublicAppService : CmsKitPublicAppServiceBase, ICommentPublicAppService
{ {
protected string RegexMarkdownUrlPattern = @"\[[^\]]*\]\((?<url>.*?)\)(?![^\x60]*\x60)";
protected ICommentRepository CommentRepository { get; } protected ICommentRepository CommentRepository { get; }
protected ICmsUserLookupService CmsUserLookupService { get; } protected ICmsUserLookupService CmsUserLookupService { get; }
public IDistributedEventBus DistributedEventBus { get; } public IDistributedEventBus DistributedEventBus { get; }
protected CommentManager CommentManager { get; } protected CommentManager CommentManager { get; }
protected IAuthorizationService AuthorizationService { get; }
protected CmsKitCommentOptions CmsCommentOptions { get; }
public CommentPublicAppService( public CommentPublicAppService(
ICommentRepository commentRepository, ICommentRepository commentRepository,
ICmsUserLookupService cmsUserLookupService, ICmsUserLookupService cmsUserLookupService,
IDistributedEventBus distributedEventBus, IDistributedEventBus distributedEventBus,
CommentManager commentManager, CommentManager commentManager,
IAuthorizationService authorizationService) IOptionsSnapshot<CmsKitCommentOptions> cmsCommentOptions)
{ {
CommentRepository = commentRepository; CommentRepository = commentRepository;
CmsUserLookupService = cmsUserLookupService; CmsUserLookupService = cmsUserLookupService;
DistributedEventBus = distributedEventBus; DistributedEventBus = distributedEventBus;
CommentManager = commentManager; CommentManager = commentManager;
AuthorizationService = authorizationService; CmsCommentOptions = cmsCommentOptions.Value;
} }
public virtual async Task<ListResultDto<CommentWithDetailsDto>> GetListAsync(string entityType, string entityId) public virtual async Task<ListResultDto<CommentWithDetailsDto>> GetListAsync(string entityType, string entityId)
@ -56,6 +61,8 @@ public class CommentPublicAppService : CmsKitPublicAppServiceBase, ICommentPubli
[Authorize] [Authorize]
public virtual async Task<CommentDto> CreateAsync(string entityType, string entityId, CreateCommentInput input) public virtual async Task<CommentDto> CreateAsync(string entityType, string entityId, CreateCommentInput input)
{ {
CheckExternalUrls(input.AllowExternalUrls, input.Text);
var user = await CmsUserLookupService.GetByIdAsync(CurrentUser.GetId()); var user = await CmsUserLookupService.GetByIdAsync(CurrentUser.GetId());
if (input.RepliedCommentId.HasValue) if (input.RepliedCommentId.HasValue)
@ -86,6 +93,8 @@ public class CommentPublicAppService : CmsKitPublicAppServiceBase, ICommentPubli
[Authorize] [Authorize]
public virtual async Task<CommentDto> UpdateAsync(Guid id, UpdateCommentInput input) public virtual async Task<CommentDto> UpdateAsync(Guid id, UpdateCommentInput input)
{ {
CheckExternalUrls(input.AllowExternalUrls, input.Text);
var comment = await CommentRepository.GetAsync(id); var comment = await CommentRepository.GetAsync(id);
if (comment.CreatorId != CurrentUser.GetId()) if (comment.CreatorId != CurrentUser.GetId())
@ -148,4 +157,41 @@ public class CommentPublicAppService : CmsKitPublicAppServiceBase, ICommentPubli
{ {
return ObjectMapper.Map<CmsUser, CmsUserDto>(comments.Single(c => c.Comment.Id == commentId).Author); return ObjectMapper.Map<CmsUser, CmsUserDto>(comments.Single(c => c.Comment.Id == commentId).Author);
} }
private void CheckExternalUrls(bool allowExternalUrls, string text)
{
if (allowExternalUrls || !CmsCommentOptions.AllowedExternalUrls.Any())
{
return;
}
var matches = Regex.Matches(text, RegexMarkdownUrlPattern,
RegexOptions.Compiled | RegexOptions.IgnoreCase);
foreach (Match match in matches)
{
if (!match.Success || match.Groups.Count < 2)
{
continue;
}
var url = match.Groups[1].Value;
if (!IsExternalUrl(url))
{
continue;
}
if (!CmsCommentOptions.AllowedExternalUrls.Contains(url.Replace("www.", "").RemovePostFix("/"),
StringComparer.InvariantCultureIgnoreCase))
{
throw new UserFriendlyException(L["UnAllowedExternalUrlMessage"]);
}
}
}
private static bool IsExternalUrl(string url)
{
return url.StartsWith("https", StringComparison.InvariantCultureIgnoreCase) ||
url.StartsWith("http", StringComparison.InvariantCultureIgnoreCase);
}
} }

2
modules/cms-kit/src/Volo.CmsKit.Public.Web/CmsKitPublicWebAutoMapperProfile.cs

@ -8,6 +8,6 @@ public class CmsKitPublicWebAutoMapperProfile : Profile
{ {
public CmsKitPublicWebAutoMapperProfile() public CmsKitPublicWebAutoMapperProfile()
{ {
CreateMap<CreateCommentWithParameteresInput, CreateCommentInput>(); CreateMap<CreateCommentWithParametersInput, CreateCommentInput>();
} }
} }

4
modules/cms-kit/src/Volo.CmsKit.Public.Web/Controllers/CmsKitPublicCommentsController.cs

@ -29,14 +29,14 @@ public class CmsKitPublicCommentsController : AbpController
} }
[HttpPost] [HttpPost]
public async Task ValidateAsync([FromBody] CreateCommentWithParameteresInput input) public async Task ValidateAsync([FromBody] CreateCommentWithParametersInput input)
{ {
if (CmsKitCommentOptions.IsRecaptchaEnabled && input.CaptchaToken.HasValue) if (CmsKitCommentOptions.IsRecaptchaEnabled && input.CaptchaToken.HasValue)
{ {
SimpleMathsCaptchaGenerator.Validate(input.CaptchaToken.Value, input.CaptchaAnswer); SimpleMathsCaptchaGenerator.Validate(input.CaptchaToken.Value, input.CaptchaAnswer);
} }
var dto = ObjectMapper.Map<CreateCommentWithParameteresInput, CreateCommentInput> (input); var dto = ObjectMapper.Map<CreateCommentWithParametersInput, CreateCommentInput> (input);
await CommentPublicAppService.CreateAsync(input.EntityType, input.EntityId, dto); await CommentPublicAppService.CreateAsync(input.EntityType, input.EntityId, dto);
} }
} }

6
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/CommentingViewComponent.cs

@ -60,7 +60,8 @@ public class CommentingViewComponent : AbpViewComponent
public virtual async Task<IViewComponentResult> InvokeAsync( public virtual async Task<IViewComponentResult> InvokeAsync(
string entityType, string entityType,
string entityId, string entityId,
IEnumerable<string> referralLinks = null) IEnumerable<string> referralLinks = null,
bool allowExternalUrls = true)
{ {
referralLinks ??= Enumerable.Empty<string>(); referralLinks ??= Enumerable.Empty<string>();
var comments = (await CommentPublicAppService var comments = (await CommentPublicAppService
@ -72,6 +73,7 @@ public class CommentingViewComponent : AbpViewComponent
{ {
EntityId = entityId, EntityId = entityId,
EntityType = entityType, EntityType = entityType,
AllowExternalUrls = allowExternalUrls,
ReferralLinks = referralLinks, ReferralLinks = referralLinks,
LoginUrl = loginUrl, LoginUrl = loginUrl,
Comments = comments.OrderByDescending(i => i.CreationTime).ToList() Comments = comments.OrderByDescending(i => i.CreationTime).ToList()
@ -121,6 +123,8 @@ public class CommentingViewComponent : AbpViewComponent
public string EntityType { get; set; } public string EntityType { get; set; }
public string EntityId { get; set; } public string EntityId { get; set; }
public bool AllowExternalUrls { get; set; }
public IEnumerable<string> ReferralLinks { get; set; } public IEnumerable<string> ReferralLinks { get; set; }

2
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/Default.cshtml

@ -32,6 +32,7 @@
style="@(string.IsNullOrEmpty(repliedCommentId?.ToString() ?? "") ? "" : "display:none")"> style="@(string.IsNullOrEmpty(repliedCommentId?.ToString() ?? "") ? "" : "display:none")">
<form class="cms-comment-form"> <form class="cms-comment-form">
<input hidden value="@(repliedCommentId?.ToString() ?? "")" name="repliedCommentId" /> <input hidden value="@(repliedCommentId?.ToString() ?? "")" name="repliedCommentId" />
<input hidden asp-for="Input.AllowExternalUrls" name="allowExternalUrls" />
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<div class="mb-3 m-0"> <div class="mb-3 m-0">
@ -120,6 +121,7 @@
<div class="card bg-light p-3 mx-0 mt-3"> <div class="card bg-light p-3 mx-0 mt-3">
<form class="cms-comment-update-form"> <form class="cms-comment-update-form">
<input hidden value="@id.ToString()" name="id" /> <input hidden value="@id.ToString()" name="id" />
<input hidden asp-for="Input.AllowExternalUrls" name="allowExternalUrls" />
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<div class="mb-3 m-0"> <div class="mb-3 m-0">

6
modules/cms-kit/src/Volo.CmsKit.Public.Web/Pages/CmsKit/Shared/Components/Commenting/default.js

@ -115,7 +115,8 @@
formAsObject.id, formAsObject.id,
{ {
text: formAsObject.commentText, text: formAsObject.commentText,
concurrencyStamp: formAsObject.commentConcurrencyStamp concurrencyStamp: formAsObject.commentConcurrencyStamp,
allowExternalUrls: formAsObject.allowExternalUrls
} }
).then(function () { ).then(function () {
widgetManager.refresh($widget); widgetManager.refresh($widget);
@ -151,7 +152,8 @@
repliedCommentId: formAsObject.repliedCommentId, repliedCommentId: formAsObject.repliedCommentId,
text: formAsObject.commentText, text: formAsObject.commentText,
captchaToken: formAsObject.captchaId, captchaToken: formAsObject.captchaId,
captchaAnswer: formAsObject.input?.captcha captchaAnswer: formAsObject.input?.captcha,
allowExternalUrls: formAsObject.allowExternalUrls
}), }),
success: function () { success: function () {
widgetManager.refresh($widget); widgetManager.refresh($widget);

Loading…
Cancel
Save