Files
OurUmbraco/uForum/Library/Utils.cs
T
2015-07-23 15:16:25 +02:00

540 lines
21 KiB
C#

using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Security;
using RestSharp;
using RestSharp.Deserializers;
using umbraco;
using umbraco.BusinessLogic;
using umbraco.editorControls.MultiNodeTreePicker;
using umbraco.NodeFactory;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web;
using Member = umbraco.cms.businesslogic.member.Member;
using MemberGroup = umbraco.cms.businesslogic.member.MemberGroup;
namespace uForum.Library
{
public class Utils
{
private const string SpamMemberGroupName = "potentialspam";
private static readonly int PotentialSpammerThreshold = int.Parse(ConfigurationManager.AppSettings["PotentialSpammerThreshold"]);
private static readonly int SpamBlockThreshold = int.Parse(ConfigurationManager.AppSettings["SpamBlockThreshold"]);
private const int ReputationThreshold = 30;
/// <summary>
/// sanitize any potentially dangerous tags from the provided raw HTML input using
/// a whitelist based approach, leaving the "safe" HTML tags
/// </summary>
public static string Sanitize(string html)
{
html = html.Replace("[code]", "<pre>");
html = html.Replace("[/code]", "</pre>");
var cleanHtml = CleanInvalidXmlChars(html);
// Add links to URLs that aren't "properly" linked in a markdown way
var regex = new Regex(@"(^|\s|>|;)(https?|ftp)(:\/\/[-A-Z0-9+&@#\/%?=~_|\[\]\(\)!:,\.;]*[-A-Z0-9+&@#\/%=~_|\[\]])($|\W)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
var linkedHtml = regex.Replace(cleanHtml, "$1<a href=\"$2$3\">$2$3</a>$4").Replace("href=\"www", "href=\"http://www");
var scriptRegex = new Regex("<script.*?</script>", RegexOptions.Singleline | RegexOptions.IgnoreCase);
var scriptRegexMatches = scriptRegex.Matches(linkedHtml);
for (var i = 0; i < scriptRegexMatches.Count; i++)
{
linkedHtml = linkedHtml.Replace(scriptRegexMatches[i].Value, string.Format("<pre>{0}</pre>", HttpContext.Current.Server.HtmlEncode(scriptRegexMatches[i].Value)));
}
var iframeRegex = new Regex("<iframe.*?</iframe>", RegexOptions.Singleline | RegexOptions.IgnoreCase);
var iframeMatches = iframeRegex.Matches(linkedHtml);
for (var i = 0; i < iframeMatches.Count; i++)
{
linkedHtml = linkedHtml.Replace(iframeMatches[i].Value, string.Format("<pre>{0}</pre>", HttpContext.Current.Server.HtmlEncode(iframeMatches[i].Value)));
}
return linkedHtml;
}
public static string CleanInvalidXmlChars(string text)
{
// From xml spec valid chars:
// #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
// any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.
const string re = @"[^\x09\x0A\x0D\x20-\xD7FF\xE000-\xFFFD\x10000-x10FFFF]";
return Regex.Replace(text, re, "");
}
public static Member GetMember(int id)
{
try
{
return Member.GetMemberFromCache(id) ?? new Member(id);
}
catch (Exception exception)
{
Log.Add(LogTypes.Error, 0, string.Format("Could not get member {0} from the cache nor from the database - Exception: {1} {2} {3}", id, exception.Message, exception.StackTrace, exception.InnerException));
}
return null;
}
public static bool IsModerator()
{
var isModerator = false;
var currentMemberId = HttpContext.Current.User.Identity.IsAuthenticated ? (int)Membership.GetUser().ProviderUserKey : 0;
if (currentMemberId != 0)
{
var moderatorRoles = new[] { "admin", "HQ", "Core", "MVP" };
isModerator = moderatorRoles.Any(moderatorRole => IsMemberInGroup(moderatorRole, currentMemberId));
}
return isModerator;
}
public static bool IsMemberInGroup(string GroupName, int memberid)
{
Member m;
try
{
m = Utils.GetMember(memberid);
}
catch (Exception ex)
{
Log.Add(LogTypes.Error, new User(0), -1, string.Format("Utills.GetMember({0}) failed - {1} {2} {3}", memberid, ex.Message, ex.StackTrace, ex.InnerException));
return false;
}
foreach (MemberGroup mg in m.Groups.Values)
{
if (mg.Text == GroupName)
return true;
}
return false;
}
public static bool IsInGroup(string GroupName)
{
if (umbraco.library.IsLoggedOn())
return IsMemberInGroup(GroupName, Member.CurrentMemberId());
else
return false;
}
public static void AddMemberToPotentialSpamGroup(Member member)
{
var memberGroup = MemberGroup.GetByName(SpamMemberGroupName);
if (memberGroup == null)
MemberGroup.MakeNew(SpamMemberGroupName, new User(0));
memberGroup = MemberGroup.GetByName(SpamMemberGroupName);
member.AddGroup(memberGroup.Id);
}
public static void RemoveMemberFromPotentialSpamGroup(Member member)
{
var memberGroup = MemberGroup.GetByName(SpamMemberGroupName);
if (memberGroup == null)
MemberGroup.MakeNew(SpamMemberGroupName, new User(0));
memberGroup = MemberGroup.GetByName(SpamMemberGroupName);
member.RemoveGroup(memberGroup.Id);
}
public static SpamResult CheckForSpam(IMember member)
{
try
{
// Already blocked, nothing left to do here
if (member.GetValue<bool>("blocked"))
{
return new SpamResult
{
MemberId = member.Id,
Name = member.Name,
Blocked = true
};
}
// If reputation is > ReputationThreshold they've got enough karma, spammers never get that far
var reputation = member.GetValue<int>("reputationTotal");
if (reputation > ReputationThreshold)
return null;
// If they're already marked as suspicious then no need to process again
if (Roles.IsUserInRole(member.Username, SpamMemberGroupName))
{
return new SpamResult
{
MemberId = member.Id,
Name = member.Name,
AlreadyInSpamRole = true
};
}
var spammer = CheckForSpam(member.Email, member.Name, false);
if (spammer != null && spammer.TotalScore > PotentialSpammerThreshold)
{
var memberService = UmbracoContext.Current.Application.Services.MemberService;
memberService.AssignRole(member.Id, SpamMemberGroupName);
spammer.MemberId = member.Id;
SendPotentialSpamMemberMail(spammer);
if (spammer.Blocked)
{
member.SetValue("blocked", true);
memberService.Save(member);
// If blocked, just redirect them to the home page where they'll get a message saying they're blocked
HttpContext.Current.Response.Redirect("/");
}
}
}
catch (Exception ex)
{
LogHelper.Error<Utils>(string.Format("Error trying to CheckForSpam for member {0}", member.Email), ex);
}
return null;
}
public static SpamResult CheckForSpam(string email, string name, bool sendMail)
{
try
{
var ipAddress = GetIpAddress();
var client = new RestClient("http://api.stopforumspam.org");
var request = new RestRequest(string.Format("api?ip={0}&email={1}&f=json", ipAddress, HttpUtility.UrlEncode(email)), Method.GET);
var response = client.Execute(request);
var jsonResult = new JsonDeserializer();
var spamCheckResult = jsonResult.Deserialize<SpamCheckResult>(response);
if (spamCheckResult.Success == 1)
{
var score = spamCheckResult.Ip.Confidence + spamCheckResult.Email.Confidence;
var blocked = score > SpamBlockThreshold;
if (score > PotentialSpammerThreshold)
{
var spammer = new SpamResult
{
Name = name,
Email = email,
Ip = ipAddress,
ScoreEmail = spamCheckResult.Email.Confidence.ToString(CultureInfo.InvariantCulture),
FrequencyEmail = spamCheckResult.Email.Frequency.ToString(CultureInfo.InvariantCulture),
ScoreIp = spamCheckResult.Ip.Confidence.ToString(CultureInfo.InvariantCulture),
FrequencyIp = spamCheckResult.Ip.Frequency.ToString(CultureInfo.InvariantCulture),
Blocked = blocked,
TotalScore = score
};
if (sendMail)
SendPotentialSpamMemberMail(spammer);
return spammer;
}
}
else
{
Log.Add(LogTypes.Error, -1, string.Format("Error checking stopforumspam.org {0}", spamCheckResult.Error));
}
}
catch (Exception ex)
{
Log.Add(LogTypes.Error, -1, string.Format("Error checking stopforumspam.org {0}", ex.Message + ex.StackTrace));
}
return null;
}
public static void SendMemberSignupMail(IMember member)
{
try
{
var ipAddress = GetIpAddress();
var client = new RestClient("http://api.stopforumspam.org");
var request = new RestRequest(string.Format("api?ip={0}&email={1}&f=json", ipAddress, HttpUtility.UrlEncode(member.Email)), Method.GET);
var response = client.Execute(request);
var jsonResult = new JsonDeserializer();
var spamCheckResult = jsonResult.Deserialize<SpamCheckResult>(response);
var spammer = new SpamResult
{
MemberId = member.Id,
Name = member.Name,
Company = member.GetValue<string>("company"),
Bio = member.GetValue<string>("profileText"),
Email = member.Email,
Ip = ipAddress
};
if (spamCheckResult.Success == 1)
{
var score = spamCheckResult.Ip.Confidence + spamCheckResult.Email.Confidence;
var blocked = score > SpamBlockThreshold;
spammer.ScoreEmail = spamCheckResult.Email.Confidence.ToString(CultureInfo.InvariantCulture);
spammer.FrequencyEmail = spamCheckResult.Email.Frequency.ToString(CultureInfo.InvariantCulture);
spammer.ScoreIp = spamCheckResult.Ip.Confidence.ToString(CultureInfo.InvariantCulture);
spammer.FrequencyIp = spamCheckResult.Ip.Frequency.ToString(CultureInfo.InvariantCulture);
spammer.Blocked = blocked;
spammer.TotalScore = score;
SendNewMemberMail(spammer);
}
else
{
LogHelper.Warn<Utils>(string.Format("Error checking stopforumspam.org {0}", spamCheckResult.Error));
}
}
catch (Exception ex)
{
LogHelper.Error<Utils>("Error checking stopforumspam.org", ex);
}
}
public static string GetIpAddress()
{
var context = HttpContext.Current;
var ipAddress = context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
if (string.IsNullOrEmpty(ipAddress))
return context.Request.ServerVariables["REMOTE_ADDR"];
var addresses = ipAddress.Split(',');
return addresses.Length != 0 ? addresses[0] : context.Request.ServerVariables["REMOTE_ADDR"];
}
public static void SendPotentialSpamMemberMail(SpamResult spammer)
{
try
{
var notify = ConfigurationManager.AppSettings["uForumSpamNotify"];
var body = GetSpamResultBody(spammer);
var subject = string.Format("Umbraco community: member {0}", spammer.Blocked ? "blocked" : "flagged as potential spammer");
var mailMessage = new MailMessage
{
Subject = subject,
Body = body,
IsBodyHtml = true
};
foreach (var email in notify.Split(','))
mailMessage.To.Add(email);
mailMessage.From = new MailAddress("our@umbraco.org");
var smtpClient = new SmtpClient();
smtpClient.Send(mailMessage);
}
catch (Exception ex)
{
Log.Add(LogTypes.Error, new User(0), -1, string.Format("Error sending potential spam member notification: {0} {1} {2}",
ex.Message, ex.StackTrace, ex.InnerException));
}
}
public static void SendNewMemberMail(SpamResult spamResult)
{
try
{
var notify = ConfigurationManager.AppSettings["uForumSpamNotify"];
var body = GetSpamResultBody(spamResult);
var subject = string.Format("Umbraco community: new member signed up");
var mailMessage = new MailMessage
{
Subject = subject,
Body = body,
IsBodyHtml = true
};
foreach (var email in notify.Split(','))
mailMessage.To.Add(email);
mailMessage.From = new MailAddress("our@umbraco.org");
var smtpClient = new SmtpClient();
smtpClient.Send(mailMessage);
}
catch (Exception ex)
{
Log.Add(LogTypes.Error, new User(0), -1, string.Format("Error sending new member notification: {0} {1} {2}",
ex.Message, ex.StackTrace, ex.InnerException));
}
}
public static void SendActivationMail(IMember member)
{
try
{
var subject = "Activate your account on our.umbraco.org";
var body =
string.Format("Hi {0},<br /><br /> Thanks for signing up for the Umbraco community site. In order to be able to log in please click on the link below to activate your account: <br /><a href=\"https://our.umbraco.org/member/activate/?id={1}\">https://our.umbraco.org/member/activate/?id={1}</a><br /><br />Best regards,<br />The Umbraco Community robot.", member.Name, member.ProviderUserKey);
var mailMessage = new MailMessage
{
Subject = subject,
Body = body,
IsBodyHtml = true
};
mailMessage.To.Add(member.Email);
mailMessage.From = new MailAddress("robot@umbraco.org");
var smtpClient = new SmtpClient();
smtpClient.Send(mailMessage);
}
catch (Exception ex)
{
var error = string.Format("*ERROR* sending activation mail for member {0} - {1} {2} {3}", member.Email, ex.Message, ex.StackTrace, ex.InnerException);
SendSlackNotification(error);
LogHelper.Error<Utils>(error, ex);
}
}
private static void SendSlackNotification(string body)
{
using (var client = new WebClient())
{
var values = new NameValueCollection
{
{"channel", ConfigurationManager.AppSettings["SlackChannel"]},
{"token", ConfigurationManager.AppSettings["SlackToken"]},
{"username", ConfigurationManager.AppSettings["SlackUsername"]},
{"icon_url", ConfigurationManager.AppSettings["SlackIconUrl"]},
{"text", body}
};
try
{
var data = client.UploadValues("https://slack.com/api/chat.postMessage", "POST", values);
var response = client.Encoding.GetString(data);
}
catch (Exception ex)
{
LogHelper.Error<Utils>("Posting update to Slack failed", ex);
}
}
}
public static string GetForumName(IPublishedContent forum)
{
var forumName = forum.Name;
var isProjectForum = string.Equals(forum.Parent.ContentType.Alias, "Project", StringComparison.InvariantCultureIgnoreCase);
if (isProjectForum && forum.Name.ToLowerInvariant().Contains(forum.Parent.Name.ToLowerInvariant()) == false)
{
forumName = forum.Parent.Name + " - " + forumName;
}
return forumName;
}
private static string GetSpamResultBody(SpamResult spammer)
{
var body = string.Empty;
if (spammer.MemberId != 0)
{
body = body + string.Format(
"<a href=\"http://our.umbraco.org/umbraco/members/editMember.aspx?id={0}\">Edit Member</a><br /><br />",
spammer.MemberId);
body = body + string.Format("<a href=\"http://our.umbraco.org/member/{0}\">Go to member</a><br />", spammer.MemberId);
}
else if (spammer.Blocked)
{
body = body + string.Format("Member was never created.<br />");
}
body = body + string.Format(
"Blocked: {0}<br />Name: {1}<br />Company: {2}<br />Bio: {3}<br />Email: {4}<br />IP: {5}<br />" +
"Score IP: {6}<br />Frequency IP: {7}<br />Score e-mail: {8}<br />Frequency e-mail: {9}<br />Total score: {10}<br />Member Id: {11}",
spammer.Blocked,
spammer.Name ?? string.Empty,
spammer.Company ?? string.Empty,
spammer.Bio == null ? string.Empty : spammer.Bio.Replace("\n", "<br />"),
spammer.Email ?? string.Empty,
spammer.Ip ?? string.Empty,
spammer.ScoreIp ?? string.Empty,
spammer.FrequencyIp ?? string.Empty,
spammer.ScoreEmail ?? string.Empty,
spammer.FrequencyEmail ?? string.Empty,
spammer.TotalScore,
spammer.MemberId);
var querystring = string.Format("api?ip={0}&email={1}&f=json", spammer.Ip, HttpUtility.UrlEncode(spammer.Email));
body = body + string.Format("<hr /><a href=\"http://api.stopforumspam.org/{0}\">Check full StopForumSpam response</a>", querystring);
return body;
}
}
public class SpamCheckResult
{
public int Success { get; set; }
public string Error { get; set; }
public SpamCheckProperty Username { get; set; }
public SpamCheckProperty Email { get; set; }
public SpamCheckProperty Ip { get; set; }
}
public class SpamCheckProperty
{
public string LastSeen { get; set; }
public int Frequency { get; set; }
public int Appears { get; set; }
public float Confidence { get; set; }
}
public class SpamResult
{
public int MemberId { get; set; }
public string Name { get; set; }
public string Company { get; set; }
public string Bio { get; set; }
public string Ip { get; set; }
public string Email { get; set; }
public string FrequencyIp { get; set; }
public string ScoreIp { get; set; }
public string ScoreEmail { get; set; }
public string FrequencyEmail { get; set; }
public bool AlreadyInSpamRole { get; set; }
public bool Blocked { get; set; }
public float TotalScore { get; set; }
}
public struct ReplacePoint
{
public int open, close;
public ReplacePoint(int open, int close)
{
this.open = open;
this.close = close;
}
}
}