Skip to content

Instantly share code, notes, and snippets.

@nul800sebastiaan
Last active September 30, 2016 09:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nul800sebastiaan/1d3cc51deedae8dbd26c33e597edd3db to your computer and use it in GitHub Desktop.
Save nul800sebastiaan/1d3cc51deedae8dbd26c33e597edd3db to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Newtonsoft.Json;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Web.HealthCheck;
namespace HealthCheckContrib
{
/// <summary>
/// Checks Umbraco backoffice users against the HaveIBeenPwned database to check if they've been breached
/// </summary>
[HealthCheck(
"E1368DBE-5066-4646-A13C-5A7C92C4105A",
"Have I Been Pwned",
Description = "Checks if any of the users in this backoffice have been pwned, as reported by https://haveibeenpwned.com",
Group = "Security")]
public class HaveIBeenPwnedCheck : HealthCheck
{
public HaveIBeenPwnedCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext)
{
_services = HealthCheckContext.ApplicationContext.Services;
}
private readonly ServiceContext _services;
/// <summary>
/// Get the status for this health check
/// </summary>
/// <returns></returns>
public override IEnumerable<HealthCheckStatus> GetStatus()
{
//return the statuses
return new[] { CheckPwnage() };
}
/// <summary>
/// Executes the action and returns it's status
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
public override HealthCheckStatus ExecuteAction(HealthCheckAction action)
{
switch (action.Alias)
{
case "sendNotificationMails":
return SendBreachNotifications();
default:
throw new ArgumentOutOfRangeException();
}
}
private HealthCheckStatus CheckPwnage()
{
int totalUsers;
var results = new List<HaveIBeenPwnedBreach>();
var allUsers = _services.UserService.GetAll(0, int.MaxValue, out totalUsers).ToArray();
var affectedEmails = new List<string>();
foreach (var user in allUsers)
{
var email = user.Email;
var breaches = CheckAccount(email).ToArray();
if (breaches.Any() == false) continue;
affectedEmails.Add(email);
results.AddRange(breaches);
}
var actions = new List<HealthCheckAction>();
if (results.Any())
actions.Add(new HealthCheckAction("sendNotificationMails", Id) { Name = "Notify users", Description = "This will send a notification email to all affected users to let them know they should change their passwords everywhere." });
return new HealthCheckStatus(string.Format("Checked {0} backoffice users, {1} breaches found, affected email addresses: {2}", totalUsers, results.Count, string.Join(",", affectedEmails)))
{
ResultType = results.Any() ? StatusResultType.Warning : StatusResultType.Success,
Actions = actions
};
}
private HealthCheckStatus SendBreachNotifications()
{
int totalUsers;
var results = new List<IUser>();
var allUsers = _services.UserService.GetAll(0, int.MaxValue, out totalUsers);
foreach (var user in allUsers)
{
var email = user.Email;
var breaches = CheckAccount(email);
if (breaches.Any())
{
//TODO: Implement sending of notification mail
results.Add(user);
}
}
return new HealthCheckStatus(string.Format("Sent notification to {0} backoffice users about breaches related to their account", results.Count))
{
ResultType = StatusResultType.Success
};
}
private IEnumerable<HaveIBeenPwnedBreach> CheckAccount(string account)
{
// Rate limited to one query per second so wait a second befor each call
Thread.Sleep(1000);
var breaches = new List<HaveIBeenPwnedBreach>();
try
{
using (var httpClient = new HttpClient())
{
const string pwnedUrl = "https://haveibeenpwned.com/api/v2/breachedaccount/";
httpClient.DefaultRequestHeaders.Add("User-Agent", "UmbracoHealthCheck");
var response = httpClient.GetAsync(new Uri(pwnedUrl + account)).Result;
if (response.IsSuccessStatusCode)
{
var jsonData = response.Content.ReadAsStringAsync().Result;
breaches = JsonConvert.DeserializeObject<List<HaveIBeenPwnedBreach>>(jsonData);
return breaches;
}
}
}
catch (Exception ex)
{
LogHelper.Error<HaveIBeenPwnedCheck>("An error occurred trying to access the HaveIBeenPwned API", ex);
}
return breaches;
}
internal class HaveIBeenPwnedBreach
{
public string Title { get; set; }
public string Name { get; set; }
public string Domain { get; set; }
public string BreachDate { get; set; }
public DateTime AddedDate { get; set; }
public int PwnCount { get; set; }
public string Description { get; set; }
public string[] DataClasses { get; set; }
public bool IsVerified { get; set; }
public bool IsSensitive { get; set; }
public bool IsRetired { get; set; }
public string LogoType { get; set; }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment