From 0527cb43bb45a127325dd80f8efc0eea528eb69d Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Wed, 7 Nov 2012 18:15:32 -0800 Subject: [PATCH] Refactoring spam filter customization --HG-- branch : 1.x --- .../DefaultCheckSpamEventHandler.cs | 28 -------- .../EventHandlers/ICheckSpamEventHandler.cs | 14 ---- .../Models/CommentCheckContext.cs | 56 ++++++++++++++++ .../Orchard.AntiSpam/Orchard.AntiSpam.csproj | 3 +- .../Services/AkismetApiSpamFilter.cs | 32 ++++----- .../Services/DefaultSpamService.cs | 67 +++++++++++-------- .../Orchard.AntiSpam/Services/ISpamFilter.cs | 14 ++-- .../Orchard.AntiSpam/Services/ISpamService.cs | 10 +-- .../Settings/SpamFilterPartSettings.cs | 8 ++- .../Settings/SpamFilterPartSettingsEvents.cs | 7 +- .../SpamFilterPartSettings.cshtml | 57 +++++++++++++--- .../Orchard.Comments/Tokens/CommentTokens.cs | 6 +- 12 files changed, 193 insertions(+), 109 deletions(-) delete mode 100644 src/Orchard.Web/Modules/Orchard.AntiSpam/EventHandlers/DefaultCheckSpamEventHandler.cs delete mode 100644 src/Orchard.Web/Modules/Orchard.AntiSpam/EventHandlers/ICheckSpamEventHandler.cs create mode 100644 src/Orchard.Web/Modules/Orchard.AntiSpam/Models/CommentCheckContext.cs diff --git a/src/Orchard.Web/Modules/Orchard.AntiSpam/EventHandlers/DefaultCheckSpamEventHandler.cs b/src/Orchard.Web/Modules/Orchard.AntiSpam/EventHandlers/DefaultCheckSpamEventHandler.cs deleted file mode 100644 index cb5d27984..000000000 --- a/src/Orchard.Web/Modules/Orchard.AntiSpam/EventHandlers/DefaultCheckSpamEventHandler.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Linq; -using Orchard.AntiSpam.Models; -using Orchard.AntiSpam.Services; -using Orchard.AntiSpam.Settings; - -namespace Orchard.AntiSpam.EventHandlers { - public class DefaultCheckSpamEventHandler : ICheckSpamEventHandler { - private readonly ISpamService _spamService; - - public DefaultCheckSpamEventHandler(ISpamService spamService) { - _spamService = spamService; - } - - public void CheckSpam(dynamic context) { - if(!_spamService.GetSpamFilters().Any()) { - return; - } - - context.Checked = true; - - if(string.IsNullOrWhiteSpace(context.Text)) { - return; - } - - context.IsSpam = _spamService.CheckForSpam(context.Text, SpamFilterAction.One, context.Content) == SpamStatus.Spam; - } - } -} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.AntiSpam/EventHandlers/ICheckSpamEventHandler.cs b/src/Orchard.Web/Modules/Orchard.AntiSpam/EventHandlers/ICheckSpamEventHandler.cs deleted file mode 100644 index 7fe2ace0f..000000000 --- a/src/Orchard.Web/Modules/Orchard.AntiSpam/EventHandlers/ICheckSpamEventHandler.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Orchard.Events; - -namespace Orchard.AntiSpam.EventHandlers { - public interface ICheckSpamEventHandler : IEventHandler { - /// - /// Dynamic object representing the parameters for the call - /// - Content (in IContent): the IContent that should trigger events when checked - /// - Text (in string): the text which is submitted for spam analysis - /// - Checked (out bool): will be assigned to true if the spam could be checked - /// - IsPam (out bool): True if the text has been reported as spam - /// - void CheckSpam(dynamic context); - } -} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.AntiSpam/Models/CommentCheckContext.cs b/src/Orchard.Web/Modules/Orchard.AntiSpam/Models/CommentCheckContext.cs new file mode 100644 index 000000000..440fc6149 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.AntiSpam/Models/CommentCheckContext.cs @@ -0,0 +1,56 @@ +namespace Orchard.AntiSpam.Models { + public class CommentCheckContext { + /// + /// The front page or home URL of the instance making the request. For a blog + /// or wiki this would be the front page. Note: Must be a full URI, including http://. + /// + public string Url { get; set; } + + /// + /// IP address of the comment submitter. + /// + public string UserIp { get; set; } + + /// + /// User agent string of the web browser submitting the comment - typically + /// the HTTP_USER_AGENT cgi variable. Not to be confused with the user agent + /// of your Akismet library. + /// + public string UserAgent { get; set; } + + /// + /// The content of the HTTP_REFERER header should be sent here. + /// + public string Referrer { get; set; } + + /// + /// The permanent location of the entry the comment was submitted to. + /// + public string Permalink { get; set; } + + /// + /// May be blank, comment, trackback, pingback, or a made up value like "registration". + /// + public string CommentType { get; set; } + + /// + /// Name submitted with the comment + /// + public string CommentAuthor { get; set; } + + /// + /// Email address submitted with the comment + /// + public string CommentAuthorEmail { get; set; } + + /// + /// URL submitted with comment + /// + public string CommentAuthorUrl { get; set; } + + /// + /// The content that was submitted. + /// + public string CommentContent { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.AntiSpam/Orchard.AntiSpam.csproj b/src/Orchard.Web/Modules/Orchard.AntiSpam/Orchard.AntiSpam.csproj index 76d8f471e..bfb12750b 100644 --- a/src/Orchard.Web/Modules/Orchard.AntiSpam/Orchard.AntiSpam.csproj +++ b/src/Orchard.Web/Modules/Orchard.AntiSpam/Orchard.AntiSpam.csproj @@ -93,8 +93,6 @@ - - @@ -102,6 +100,7 @@ + diff --git a/src/Orchard.Web/Modules/Orchard.AntiSpam/Services/AkismetApiSpamFilter.cs b/src/Orchard.Web/Modules/Orchard.AntiSpam/Services/AkismetApiSpamFilter.cs index 7304b37d4..af614493c 100644 --- a/src/Orchard.Web/Modules/Orchard.AntiSpam/Services/AkismetApiSpamFilter.cs +++ b/src/Orchard.Web/Modules/Orchard.AntiSpam/Services/AkismetApiSpamFilter.cs @@ -6,7 +6,6 @@ using System.Text; using System.Web; using Orchard.AntiSpam.Models; using Orchard.Logging; -using Orchard.Utility.Extensions; namespace Orchard.AntiSpam.Services { public class AkismetApiSpamFilter : ISpamFilter { @@ -26,9 +25,9 @@ namespace Orchard.AntiSpam.Services { public ILogger Logger { get; set; } - public SpamStatus CheckForSpam(string text) { + public SpamStatus CheckForSpam(CommentCheckContext context) { try { - var result = ExecuteValidateRequest(text, "comment-check"); + var result = ExecuteValidateRequest(context, "comment-check"); if (HandleValidateResponse(_context, result)) { return SpamStatus.Spam; @@ -42,25 +41,25 @@ namespace Orchard.AntiSpam.Services { } } - public void ReportSpam(string text) { + public void ReportSpam(CommentCheckContext context) { try { - var result = ExecuteValidateRequest(text, "submit-spam"); + var result = ExecuteValidateRequest(context, "submit-spam"); } catch (Exception e) { Logger.Error(e, "An error occured while reporting spam"); } } - public void ReportHam(string text) { + public void ReportHam(CommentCheckContext context) { try { - var result = ExecuteValidateRequest(text, "submit-ham"); + var result = ExecuteValidateRequest(context, "submit-ham"); } catch (Exception e) { Logger.Error(e, "An error occured while reporting ham"); } } - private string ExecuteValidateRequest(string text, string action) { + private string ExecuteValidateRequest(CommentCheckContext context, string action) { var uri = String.Format(AkismetApiPattern, _apiKey, _endpoint, action); WebRequest request = WebRequest.Create(uri); @@ -68,13 +67,16 @@ namespace Orchard.AntiSpam.Services { request.Timeout = 5000; //milliseconds request.ContentType = "application/x-www-form-urlencoded"; - var postData = String.Format(CultureInfo.InvariantCulture, "blog={0}&user_ip={1}&user_agent={2}&referrer={3}&comment_content={4}", - HttpUtility.UrlEncode(_context.Request.ToApplicationRootUrlString()), - HttpUtility.UrlEncode(_context.Request.ServerVariables["REMOTE_ADDR"]), - HttpUtility.UrlEncode(_context.Request.UserAgent), - HttpUtility.UrlEncode(Convert.ToString(_context.Request.UrlReferrer)), - HttpUtility.UrlEncode(text) - ); + var postData = "blog=" + HttpUtility.UrlEncode(context.Url) + + "&user_ip=" + HttpUtility.UrlEncode(context.UserIp) + + "&user_agent=" + HttpUtility.UrlEncode(context.UserAgent) + + "&referrer=" + HttpUtility.UrlEncode(context.Referrer) + + "&permalink=" + HttpUtility.UrlEncode(context.Permalink) + + "&comment_type=" + HttpUtility.UrlEncode(context.CommentType) + + "&comment_author=" + HttpUtility.UrlEncode(context.CommentAuthor) + + "&comment_author_email=" + HttpUtility.UrlEncode(context.CommentAuthorEmail) + + "&comment_author_url=" + HttpUtility.UrlEncode(context.CommentAuthorUrl) + + "&comment_content=" + HttpUtility.UrlEncode(context.CommentContent); byte[] content = Encoding.UTF8.GetBytes(postData); using (Stream stream = request.GetRequestStream()) { diff --git a/src/Orchard.Web/Modules/Orchard.AntiSpam/Services/DefaultSpamService.cs b/src/Orchard.Web/Modules/Orchard.AntiSpam/Services/DefaultSpamService.cs index 18b6da710..970ca1f51 100644 --- a/src/Orchard.Web/Modules/Orchard.AntiSpam/Services/DefaultSpamService.cs +++ b/src/Orchard.Web/Modules/Orchard.AntiSpam/Services/DefaultSpamService.cs @@ -13,22 +13,25 @@ namespace Orchard.AntiSpam.Services { private readonly IEnumerable _providers; private readonly ISpamEventHandler _spamEventHandler; private readonly IRulesManager _rulesManager; + private readonly IWorkContextAccessor _workContextAccessor; public DefaultSpamService( ITokenizer tokenizer, IEnumerable providers, ISpamEventHandler spamEventHandler, - IRulesManager rulesManager + IRulesManager rulesManager, + IWorkContextAccessor workContextAccessor ) { _tokenizer = tokenizer; _providers = providers; _spamEventHandler = spamEventHandler; _rulesManager = rulesManager; + _workContextAccessor = workContextAccessor; } - public SpamStatus CheckForSpam(string text, SpamFilterAction action, IContent content) { + public SpamStatus CheckForSpam(CommentCheckContext context, SpamFilterAction action, IContent content) { - if (string.IsNullOrWhiteSpace(text)) { + if (string.IsNullOrWhiteSpace(context.CommentContent)) { return SpamStatus.Ham; } @@ -38,13 +41,13 @@ namespace Orchard.AntiSpam.Services { switch (action) { case SpamFilterAction.AllOrNothing: - if (spamFilters.All(x => x.CheckForSpam(text) == SpamStatus.Spam)) { + if (spamFilters.All(x => x.CheckForSpam(context) == SpamStatus.Spam)) { result = SpamStatus.Spam; } break; case SpamFilterAction.One: - if (spamFilters.Any(x => x.CheckForSpam(text) == SpamStatus.Spam)) { + if (spamFilters.Any(x => x.CheckForSpam(context) == SpamStatus.Spam)) { result = SpamStatus.Spam; } @@ -71,57 +74,67 @@ namespace Orchard.AntiSpam.Services { } public SpamStatus CheckForSpam(SpamFilterPart part) { - var settings = part.TypePartDefinition.Settings.GetModel(); + var context = CreateCommentCheckContext(part, _workContextAccessor.GetContext()); - // evaluate the text to submit to the spam filters - var text = _tokenizer.Replace(settings.Pattern, new Dictionary { { "Content", part.ContentItem } }); - - if (string.IsNullOrWhiteSpace(text)) { + if (string.IsNullOrWhiteSpace(context.CommentContent)) { return SpamStatus.Ham; } - var result = CheckForSpam(text, settings.Action, part); + var result = CheckForSpam(context, settings.Action, part); return result; } - public void ReportSpam(string text) { + public void ReportSpam(CommentCheckContext context) { var spamFilters = GetSpamFilters().ToList(); foreach(var filter in spamFilters) { - filter.ReportSpam(text); + filter.ReportSpam(context); } } public void ReportSpam(SpamFilterPart part) { - var settings = part.TypePartDefinition.Settings.GetModel(); - - // evaluate the text to submit to the spam filters - var text = _tokenizer.Replace(settings.Pattern, new Dictionary { { "Content", part.ContentItem } }); - - ReportSpam(text); + ReportSpam(CreateCommentCheckContext(part, _workContextAccessor.GetContext())); } - public void ReportHam(string text) { + public void ReportHam(CommentCheckContext context) { var spamFilters = GetSpamFilters().ToList(); foreach (var filter in spamFilters) { - filter.ReportHam(text); + filter.ReportHam(context); } } public void ReportHam(SpamFilterPart part) { - var settings = part.TypePartDefinition.Settings.GetModel(); - - // evaluate the text to submit to the spam filters - var text = _tokenizer.Replace(settings.Pattern, new Dictionary { { "Content", part.ContentItem } }); - - ReportHam(text); + ReportHam(CreateCommentCheckContext(part, _workContextAccessor.GetContext())); } public IEnumerable GetSpamFilters() { return _providers.SelectMany(x => x.GetSpamFilters()).Where(x => x != null); } + + private CommentCheckContext CreateCommentCheckContext(SpamFilterPart part, WorkContext workContext) { + var settings = part.TypePartDefinition.Settings.GetModel(); + + var data = new Dictionary {{"Content", part.ContentItem}}; + + var context = new CommentCheckContext { + Url = _tokenizer.Replace(settings.UrlPattern, data), + Permalink = _tokenizer.Replace(settings.PermalinkPattern, data), + CommentAuthor = _tokenizer.Replace(settings.CommentAuthorPattern, data), + CommentAuthorEmail = _tokenizer.Replace(settings.CommentAuthorEmailPattern, data), + CommentAuthorUrl = _tokenizer.Replace(settings.CommentAuthorUrlPattern, data), + CommentContent = _tokenizer.Replace(settings.CommentContentPattern, data), + }; + + if(workContext.HttpContext != null) { + context.UserIp = workContext.HttpContext.Request.ServerVariables["REMOTE_ADDR"]; + context.UserAgent = workContext.HttpContext.Request.UserAgent; + context.Referrer = Convert.ToString(workContext.HttpContext.Request.UrlReferrer); + } + + return context; + } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.AntiSpam/Services/ISpamFilter.cs b/src/Orchard.Web/Modules/Orchard.AntiSpam/Services/ISpamFilter.cs index 2a11f06b6..653c140d6 100644 --- a/src/Orchard.Web/Modules/Orchard.AntiSpam/Services/ISpamFilter.cs +++ b/src/Orchard.Web/Modules/Orchard.AntiSpam/Services/ISpamFilter.cs @@ -9,20 +9,20 @@ namespace Orchard.AntiSpam.Services { /// /// Checks if some content is spam. /// - /// The text to check. - /// SpamStatus.Spam if the text has been categorized as spam, SpamStatus.Ham otherwise. - SpamStatus CheckForSpam(string text); + /// The comment to check. + /// SpamStatus.Spam if the comment has been categorized as spam, SpamStatus.Ham otherwise. + SpamStatus CheckForSpam(CommentCheckContext context); /// /// Explicitely report some content as spam in order to improve the service. /// - /// The text to report as spam. - void ReportSpam(string text); + /// The comment to report as spam. + void ReportSpam(CommentCheckContext context); /// /// Explicitely report some content as ham in order to improve the service. /// - /// The text to report as ham (false positive). - void ReportHam(string text); + /// The comment to report as ham (false positive). + void ReportHam(CommentCheckContext context); } } diff --git a/src/Orchard.Web/Modules/Orchard.AntiSpam/Services/ISpamService.cs b/src/Orchard.Web/Modules/Orchard.AntiSpam/Services/ISpamService.cs index 60843971e..36932aa4b 100644 --- a/src/Orchard.Web/Modules/Orchard.AntiSpam/Services/ISpamService.cs +++ b/src/Orchard.Web/Modules/Orchard.AntiSpam/Services/ISpamService.cs @@ -5,14 +5,14 @@ using Orchard.ContentManagement; namespace Orchard.AntiSpam.Services { public interface ISpamService : IDependency { - SpamStatus CheckForSpam(string text, SpamFilterAction action, IContent content); + SpamStatus CheckForSpam(CommentCheckContext text, SpamFilterAction action, IContent content); SpamStatus CheckForSpam(SpamFilterPart part); /// /// Explicitely report some content as spam in order to improve the service. /// - /// The text to report as spam. - void ReportSpam(string text); + /// The comment context to report as spam. + void ReportSpam(CommentCheckContext context); /// /// Explicitely report some content as ham in order to improve the service. @@ -23,8 +23,8 @@ namespace Orchard.AntiSpam.Services { /// /// Explicitely report some content as ham in order to improve the service. /// - /// The text to report as ham (false positive). - void ReportHam(string text); + /// The comment context to report as ham (false positive). + void ReportHam(CommentCheckContext context); /// /// Explicitely report some content as ham in order to improve the service. diff --git a/src/Orchard.Web/Modules/Orchard.AntiSpam/Settings/SpamFilterPartSettings.cs b/src/Orchard.Web/Modules/Orchard.AntiSpam/Settings/SpamFilterPartSettings.cs index 6a44c4580..66230d09a 100644 --- a/src/Orchard.Web/Modules/Orchard.AntiSpam/Settings/SpamFilterPartSettings.cs +++ b/src/Orchard.Web/Modules/Orchard.AntiSpam/Settings/SpamFilterPartSettings.cs @@ -1,8 +1,14 @@ namespace Orchard.AntiSpam.Settings { public class SpamFilterPartSettings { public SpamFilterAction Action { get; set; } - public string Pattern { get; set; } public bool DeleteSpam { get; set; } + + public string UrlPattern { get; set; } + public string PermalinkPattern { get; set; } + public string CommentAuthorPattern { get; set; } + public string CommentAuthorEmailPattern { get; set; } + public string CommentAuthorUrlPattern { get; set; } + public string CommentContentPattern { get; set; } } /// diff --git a/src/Orchard.Web/Modules/Orchard.AntiSpam/Settings/SpamFilterPartSettingsEvents.cs b/src/Orchard.Web/Modules/Orchard.AntiSpam/Settings/SpamFilterPartSettingsEvents.cs index d98bdb8f2..d0660ee73 100644 --- a/src/Orchard.Web/Modules/Orchard.AntiSpam/Settings/SpamFilterPartSettingsEvents.cs +++ b/src/Orchard.Web/Modules/Orchard.AntiSpam/Settings/SpamFilterPartSettingsEvents.cs @@ -30,8 +30,13 @@ namespace Orchard.AntiSpam.Settings { if (updateModel.TryUpdateModel(settings, "SpamFilterPartSettings", null, null)) { builder.WithSetting("SpamFilterPartSettings.Action", settings.Action.ToString()); - builder.WithSetting("SpamFilterPartSettings.Pattern", settings.Pattern); builder.WithSetting("SpamFilterPartSettings.DeleteSpam", settings.DeleteSpam.ToString(CultureInfo.InvariantCulture)); + builder.WithSetting("SpamFilterPartSettings.UrlPattern", settings.UrlPattern); + builder.WithSetting("SpamFilterPartSettings.PermalinkPattern", settings.PermalinkPattern); + builder.WithSetting("SpamFilterPartSettings.CommentAuthorPattern", settings.CommentAuthorPattern); + builder.WithSetting("SpamFilterPartSettings.CommentAuthorUrlPattern", settings.CommentAuthorUrlPattern); + builder.WithSetting("SpamFilterPartSettings.CommentAuthorEmailPattern", settings.CommentAuthorEmailPattern); + builder.WithSetting("SpamFilterPartSettings.CommentContentPattern", settings.CommentContentPattern); } yield return DefinitionTemplate(settings); diff --git a/src/Orchard.Web/Modules/Orchard.AntiSpam/Views/DefinitionTemplates/SpamFilterPartSettings.cshtml b/src/Orchard.Web/Modules/Orchard.AntiSpam/Views/DefinitionTemplates/SpamFilterPartSettings.cshtml index 79d8ee123..46864c3f5 100644 --- a/src/Orchard.Web/Modules/Orchard.AntiSpam/Views/DefinitionTemplates/SpamFilterPartSettings.cshtml +++ b/src/Orchard.Web/Modules/Orchard.AntiSpam/Views/DefinitionTemplates/SpamFilterPartSettings.cshtml @@ -15,14 +15,6 @@ -
- -
- @Html.TextBoxFor(m => m.Pattern, new { @class = "text large tokenized" }) - @T("The tokenized pattern generating the text to submit to spam filters.") -
-
-
@Html.EditorFor(m => m.DeleteSpam) @@ -30,3 +22,52 @@ @T("Enable to have spam automatically deleted when found. You won't be able to find false positive.")
+ +
+ +
+ @Html.TextBoxFor(m => m.UrlPattern, new { @class = "text large tokenized" }) + @T("The front page or home URL of the instance making the request. For a blog or wiki this would be the front page. Note: Must be a full URI, including http://.") +
+
+ + +
+ +
+ @Html.TextBoxFor(m => m.PermalinkPattern, new { @class = "text large tokenized" }) + @T("The permanent location of the entry the content item was submitted to.") +
+
+ +
+ +
+ @Html.TextBoxFor(m => m.CommentAuthorPattern, new { @class = "text large tokenized" }) + @T("Name submitted with the content item.") +
+
+ +
+ +
+ @Html.TextBoxFor(m => m.CommentAuthorEmailPattern, new { @class = "text large tokenized" }) + @T("Email address submitted with the content item.") +
+
+ +
+ +
+ @Html.TextBoxFor(m => m.CommentAuthorUrlPattern, new { @class = "text large tokenized" }) + @T("URL submitted with the content item.") +
+
+ +
+ +
+ @Html.TextBoxFor(m => m.CommentContentPattern, new { @class = "text large tokenized" }) + @T("The content that was submitted.") +
+
diff --git a/src/Orchard.Web/Modules/Orchard.Comments/Tokens/CommentTokens.cs b/src/Orchard.Web/Modules/Orchard.Comments/Tokens/CommentTokens.cs index 39176a1a1..c485e2887 100644 --- a/src/Orchard.Web/Modules/Orchard.Comments/Tokens/CommentTokens.cs +++ b/src/Orchard.Web/Modules/Orchard.Comments/Tokens/CommentTokens.cs @@ -21,10 +21,12 @@ namespace Orchard.Comments.Tokens { public Localizer T { get; set; } public void Describe(dynamic context) { - context.For("Content", T("Content Items"), T("Content Items")) + context.For("Content", T("Comments"), T("Comments")) .Token("CommentedOn", T("Commented On"), T("The content item this comment was created on.")) .Token("CommentMessage", T("Comment Message"), T("The text of the comment itself")) .Token("CommentAuthor", T("Comment Author"), T("The author of the comment.")) + .Token("CommentAuthorUrl", T("Comment Author Url"), T("The url provided by the author of the comment.")) + .Token("CommentAuthorEmail", T("Comment Author Email"), T("The email provided by the author of the comment.")) ; } @@ -34,6 +36,8 @@ namespace Orchard.Comments.Tokens { .Chain("CommentedOn", "Content", (Func)(content => _contentManager.Get(content.As().CommentedOn))) .Token("CommentMessage", (Func)(content => content.As().CommentText)) .Token("CommentAuthor", (Func)CommentAuthor) + .Token("CommentAuthorUrl", (Func)(content => content.As().SiteName)) + .Token("CommentAuthorEmail", (Func)(content => content.As().Email)) ; }