2017-06-04 14:34:37 +02:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Net;
|
|
|
|
|
|
using System.Text.RegularExpressions;
|
2020-10-21 10:29:25 +01:00
|
|
|
|
using Microsoft.Extensions.Configuration;
|
2017-06-04 14:34:37 +02:00
|
|
|
|
using Umbraco.Core.Services;
|
2020-10-21 10:29:25 +01:00
|
|
|
|
using Umbraco.Web;
|
2017-06-04 14:34:37 +02:00
|
|
|
|
|
2020-10-21 10:29:25 +01:00
|
|
|
|
namespace Umbraco.Core.HealthCheck.Checks.Security
|
2017-06-04 14:34:37 +02:00
|
|
|
|
{
|
|
|
|
|
|
public abstract class BaseHttpHeaderCheck : HealthCheck
|
|
|
|
|
|
{
|
2018-04-19 23:41:35 +10:00
|
|
|
|
protected ILocalizedTextService TextService { get; }
|
2017-06-04 14:34:37 +02:00
|
|
|
|
|
|
|
|
|
|
private const string SetHeaderInConfigAction = "setHeaderInConfig";
|
|
|
|
|
|
|
|
|
|
|
|
private readonly string _header;
|
|
|
|
|
|
private readonly string _value;
|
|
|
|
|
|
private readonly string _localizedTextPrefix;
|
|
|
|
|
|
private readonly bool _metaTagOptionAvailable;
|
2020-05-07 09:34:16 +02:00
|
|
|
|
private readonly IRequestAccessor _requestAccessor;
|
2017-06-04 14:34:37 +02:00
|
|
|
|
|
2018-04-19 23:41:35 +10:00
|
|
|
|
protected BaseHttpHeaderCheck(
|
2020-05-07 09:34:16 +02:00
|
|
|
|
IRequestAccessor requestAccessor,
|
2018-04-19 23:41:35 +10:00
|
|
|
|
ILocalizedTextService textService,
|
2020-10-21 10:29:25 +01:00
|
|
|
|
string header, string value, string localizedTextPrefix, bool metaTagOptionAvailable)
|
2017-06-04 14:34:37 +02:00
|
|
|
|
{
|
2018-04-19 23:41:35 +10:00
|
|
|
|
TextService = textService ?? throw new ArgumentNullException(nameof(textService));
|
2020-05-07 09:34:16 +02:00
|
|
|
|
_requestAccessor = requestAccessor;
|
2017-06-04 14:34:37 +02:00
|
|
|
|
_header = header;
|
|
|
|
|
|
_value = value;
|
|
|
|
|
|
_localizedTextPrefix = localizedTextPrefix;
|
|
|
|
|
|
_metaTagOptionAvailable = metaTagOptionAvailable;
|
2020-01-27 16:38:02 +01:00
|
|
|
|
|
2017-06-04 14:34:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Get the status for this health check
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public override IEnumerable<HealthCheckStatus> GetStatus()
|
|
|
|
|
|
{
|
|
|
|
|
|
//return the statuses
|
|
|
|
|
|
return new[] { CheckForHeader() };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <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 SetHeaderInConfigAction:
|
|
|
|
|
|
return SetHeaderInConfig();
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new InvalidOperationException("HTTP Header action requested is either not executable or does not exist");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected HealthCheckStatus CheckForHeader()
|
|
|
|
|
|
{
|
|
|
|
|
|
var message = string.Empty;
|
|
|
|
|
|
var success = false;
|
|
|
|
|
|
|
|
|
|
|
|
// Access the site home page and check for the click-jack protection header or meta tag
|
2020-10-21 10:29:25 +01:00
|
|
|
|
var url = _requestAccessor.GetApplicationUrl();
|
2018-04-12 17:59:11 +02:00
|
|
|
|
var request = WebRequest.Create(url);
|
2017-06-04 14:34:37 +02:00
|
|
|
|
request.Method = "GET";
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var response = request.GetResponse();
|
|
|
|
|
|
|
|
|
|
|
|
// Check first for header
|
2019-10-28 17:04:20 +00:00
|
|
|
|
success = HasMatchingHeader(response.Headers.AllKeys);
|
2017-06-04 14:34:37 +02:00
|
|
|
|
|
|
|
|
|
|
// If not found, and available, check for meta-tag
|
|
|
|
|
|
if (success == false && _metaTagOptionAvailable)
|
|
|
|
|
|
{
|
|
|
|
|
|
success = DoMetaTagsContainKeyForHeader(response);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
message = success
|
2018-04-19 23:41:35 +10:00
|
|
|
|
? TextService.Localize($"healthcheck/{_localizedTextPrefix}CheckHeaderFound")
|
|
|
|
|
|
: TextService.Localize($"healthcheck/{_localizedTextPrefix}CheckHeaderNotFound");
|
2017-06-04 14:34:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2018-04-19 23:41:35 +10:00
|
|
|
|
message = TextService.Localize("healthcheck/healthCheckInvalidUrl", new[] { url.ToString(), ex.Message });
|
2017-06-04 14:34:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var actions = new List<HealthCheckAction>();
|
|
|
|
|
|
if (success == false)
|
|
|
|
|
|
{
|
|
|
|
|
|
actions.Add(new HealthCheckAction(SetHeaderInConfigAction, Id)
|
|
|
|
|
|
{
|
2018-04-19 23:41:35 +10:00
|
|
|
|
Name = TextService.Localize("healthcheck/setHeaderInConfig"),
|
|
|
|
|
|
Description = TextService.Localize($"healthcheck/{_localizedTextPrefix}SetHeaderInConfigDescription")
|
2017-06-04 14:34:37 +02:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
new HealthCheckStatus(message)
|
|
|
|
|
|
{
|
|
|
|
|
|
ResultType = success ? StatusResultType.Success : StatusResultType.Error,
|
|
|
|
|
|
Actions = actions
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-10-28 17:04:20 +00:00
|
|
|
|
private bool HasMatchingHeader(IEnumerable<string> headerKeys)
|
2017-06-04 14:34:37 +02:00
|
|
|
|
{
|
2019-10-28 17:04:20 +00:00
|
|
|
|
return headerKeys.Contains(_header, StringComparer.InvariantCultureIgnoreCase);
|
2017-06-04 14:34:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private bool DoMetaTagsContainKeyForHeader(WebResponse response)
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var stream = response.GetResponseStream())
|
|
|
|
|
|
{
|
|
|
|
|
|
if (stream == null) return false;
|
|
|
|
|
|
using (var reader = new StreamReader(stream))
|
|
|
|
|
|
{
|
|
|
|
|
|
var html = reader.ReadToEnd();
|
|
|
|
|
|
var metaTags = ParseMetaTags(html);
|
2019-10-28 17:04:20 +00:00
|
|
|
|
return HasMatchingHeader(metaTags.Keys);
|
2017-06-04 14:34:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static Dictionary<string, string> ParseMetaTags(string html)
|
|
|
|
|
|
{
|
|
|
|
|
|
var regex = new Regex("<meta http-equiv=\"(.+?)\" content=\"(.+?)\"", RegexOptions.IgnoreCase);
|
|
|
|
|
|
|
|
|
|
|
|
return regex.Matches(html)
|
|
|
|
|
|
.Cast<Match>()
|
|
|
|
|
|
.ToDictionary(m => m.Groups[1].Value, m => m.Groups[2].Value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private HealthCheckStatus SetHeaderInConfig()
|
|
|
|
|
|
{
|
|
|
|
|
|
var errorMessage = string.Empty;
|
2020-10-21 10:29:25 +01:00
|
|
|
|
//TODO: edit to show fix suggestion instead of making fix
|
|
|
|
|
|
var success = true;
|
2017-06-04 14:34:37 +02:00
|
|
|
|
|
|
|
|
|
|
if (success)
|
|
|
|
|
|
{
|
|
|
|
|
|
return
|
2018-04-19 23:41:35 +10:00
|
|
|
|
new HealthCheckStatus(TextService.Localize(string.Format("healthcheck/{0}SetHeaderInConfigSuccess", _localizedTextPrefix)))
|
2017-06-04 14:34:37 +02:00
|
|
|
|
{
|
|
|
|
|
|
ResultType = StatusResultType.Success
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return
|
2020-10-21 10:29:25 +01:00
|
|
|
|
new HealthCheckStatus(TextService.Localize("healthcheck/setHeaderInConfigError", new[] { errorMessage }))
|
2017-06-04 14:34:37 +02:00
|
|
|
|
{
|
|
|
|
|
|
ResultType = StatusResultType.Error
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|