Skip to content

Commit

Permalink
Merge pull request CenterEdge#6 from CenterEdge/resourcefiltering
Browse files Browse the repository at this point in the history
Allow customization of resource filtering for security
  • Loading branch information
ybonner committed Dec 13, 2016
2 parents 5c5982d + 9a472f9 commit 3c8fcd5
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 10 deletions.
1 change: 1 addition & 0 deletions src/Positron.UI/Builder/PositronUiBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ private IServiceCollection BuildServices(CefSettings settings)
//This is required to add ILogger of T.
services.AddLogging();

services.TryAddSingleton<IResourceRequestFilter, PositronOnlyResourceRequestFilter>();
services.TryAddSingleton<IBrowserProcessHandler, BrowserProcessHandler>();
services.TryAddSingleton<IRequestHandler, RequestHandler>();
services.TryAddSingleton<IResourceHandlerFactory, PositronResourceHandlerFactory>();
Expand Down
58 changes: 58 additions & 0 deletions src/Positron.UI/HostResourceRequestFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Positron.UI
{
/// <summary>
/// Base class for a resource request filter that filters based on scheme and host.
/// </summary>
public abstract class HostResourceRequestFilter : IResourceRequestFilter
{
/// <summary>
/// List of valid schemes, i.e. "http" and "https".
/// </summary>
public abstract HashSet<string> ValidSchemes { get; }

/// <summary>
/// List of valid hosts, i.e. "positron" or "google.com".
/// </summary>
public abstract HashSet<string> ValidHosts { get; }

/// <inheritdoc cref="IResourceRequestFilter"/>
public virtual Task<bool> CanLoadResourceAsync(ResourceRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Url == null)
{
throw new ArgumentException("ResourceRequestContext.Url may not be null.", nameof(context));
}
if (!context.Url.IsAbsoluteUri)
{
throw new ArgumentException("ResourceRequestContext.Url must be absolute.", nameof(context));
}

var result = ValidSchemes.Contains(context.Url.Scheme) && ValidHosts.Contains(context.Url.Host);

if (!result)
{
OnResourceRejection(context);
}

return Task.FromResult(result);
}

/// <summary>
/// Called when a resource is rejected to permit logging.
/// </summary>
/// <param name="context"><see cref="ResourceRequestContext"/> of the resource being rejected.</param>
public virtual void OnResourceRejection(ResourceRequestContext context)
{
}
}
}
21 changes: 21 additions & 0 deletions src/Positron.UI/IResourceRequestFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Positron.UI
{
/// <summary>
/// Filter requests for resource load to provide application security
/// </summary>
public interface IResourceRequestFilter
{
/// <summary>
/// Evaluates a request and returns true if the resource may be safely loaded in Chromium.
/// </summary>
/// <param name="context"><see cref="ResourceRequestContext"/> to evaluate.</param>
/// <returns>True if the resource may be safely loaded in Chromium.</returns>
Task<bool> CanLoadResourceAsync(ResourceRequestContext context);
}
}
1 change: 1 addition & 0 deletions src/Positron.UI/Internal/LoggerEventIds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ internal static class LoggerEventIds

// ResourceHandler
public const int ExternalResource = 500;
public const int ResourceRequestFilterError = 501;
}
}
28 changes: 28 additions & 0 deletions src/Positron.UI/Internal/PositronOnlyResourceRequestFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CefSharp;
using Microsoft.Extensions.Logging;

namespace Positron.UI.Internal
{
internal class PositronOnlyResourceRequestFilter : HostResourceRequestFilter
{
private readonly ILogger<PositronOnlyResourceRequestFilter> _logger;

public override HashSet<string> ValidSchemes { get; } = new HashSet<string> {"http"};
public override HashSet<string> ValidHosts { get; } = new HashSet<string> {"positron"};

public PositronOnlyResourceRequestFilter(ILogger<PositronOnlyResourceRequestFilter> logger)
{
_logger = logger;
}

public override void OnResourceRejection(ResourceRequestContext context)
{
_logger.LogWarning(LoggerEventIds.ExternalResource, "Preventing load of external resource '{0}'", context.Url);
}
}
}
55 changes: 46 additions & 9 deletions src/Positron.UI/Internal/RequestHandler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Windows.Shell;
using CefSharp;
using Microsoft.Extensions.Logging;

Expand All @@ -7,15 +8,21 @@ namespace Positron.UI.Internal
internal class RequestHandler : IRequestHandler
{
private readonly ILogger<RequestHandler> _logger;
private readonly IResourceRequestFilter _requestFilter;

public RequestHandler(ILogger<RequestHandler> logger)
public RequestHandler(ILogger<RequestHandler> logger, IResourceRequestFilter requestFilter)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
if (requestFilter == null)
{
throw new ArgumentNullException(nameof(requestFilter));
}

_logger = logger;
_requestFilter = requestFilter;
}

public bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool isRedirect)
Expand Down Expand Up @@ -43,15 +50,45 @@ public virtual void OnPluginCrashed(IWebBrowser browserControl, IBrowser browser
public virtual CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request,
IRequestCallback callback)
{
if (!request.Url.StartsWith("http://positron/"))
var context = new ResourceRequestContext
{
_logger.LogWarning(LoggerEventIds.ExternalResource, "Preventing load of external resource '{0}'", request.Url);

callback.Dispose();
return CefReturnValue.Cancel;
}

return CefReturnValue.Continue;
Method = request.Method,
Referrer = !string.IsNullOrEmpty(request.ReferrerUrl) ? new Uri(request.ReferrerUrl) : null,
Url = new Uri(request.Url)
};

_requestFilter.CanLoadResourceAsync(context)
.ContinueWith(task =>
{
using (callback)
{
if (!task.IsCompleted)
{
if (task.Exception != null)
{
foreach (var ex in task.Exception.Flatten().InnerExceptions)
{
_logger.LogError(LoggerEventIds.ResourceRequestFilterError, ex,
"Error processing IResourceRequestFilter for url '{0}'", context.Url);
}
}
else
{
_logger.LogError(LoggerEventIds.ResourceRequestFilterError,
"Error processing IResourceRequestFilter for url '{0}', no exception returned",
context.Url);
}
callback.Cancel();
}
else
{
callback.Continue(task.Result);
}
}
});

return CefReturnValue.ContinueAsync;
}

public bool GetAuthCredentials(IWebBrowser browserControl, IBrowser browser, IFrame frame, bool isProxy, string host, int port,
Expand Down
4 changes: 4 additions & 0 deletions src/Positron.UI/Positron.UI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Builder\PositronUiBuilderExtensions.cs" />
<Compile Include="HostResourceRequestFilter.cs" />
<Compile Include="Internal\LoggerEventIds.cs" />
<Compile Include="Internal\PositronOnlyResourceRequestFilter.cs" />
<Compile Include="Internal\PositronResourceHandlerFactory.cs" />
<Compile Include="Internal\PositronResourceHandler.cs" />
<Compile Include="Internal\BrowserProcessHandler.cs" />
Expand All @@ -62,6 +64,7 @@
<Compile Include="IConsoleLogger.cs" />
<Compile Include="Internal\NullConsoleLogger.cs" />
<Compile Include="Internal\KeyboardHandler.cs" />
<Compile Include="IResourceRequestFilter.cs" />
<Compile Include="PositronWindow.xaml.cs">
<DependentUpon>PositronWindow.xaml</DependentUpon>
</Compile>
Expand All @@ -72,6 +75,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Internal\RequestHandler.cs" />
<Compile Include="Internal\PositronUi.cs" />
<Compile Include="ResourceRequestContext.cs" />
</ItemGroup>
<ItemGroup>
<None Include="project.json" />
Expand Down
29 changes: 29 additions & 0 deletions src/Positron.UI/ResourceRequestContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Positron.UI
{
/// <summary>
/// Information about a Chromium request before it is processed.
/// </summary>
public class ResourceRequestContext
{
/// <summary>
/// Request method, i.e. "GET" or "POST".
/// </summary>
public string Method { get; set; }

/// <summary>
/// <see cref="Uri"/> of the referrer, if any.
/// </summary>
public Uri Referrer { get; set; }

/// <summary>
/// <see cref="Uri"/> being requested. Should be an absolute Uri.
/// </summary>
public Uri Url { get; set; }
}
}
3 changes: 2 additions & 1 deletion test/Positron.Application/Views/Home/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
<a href="javascript: console.log('Test Log');">Console Logging Test</a><br />
<a href="#" id="TestAjax">Ajax Request</a><br />
<a href="#" id="TestId">ID Route With Space Test</a><br />
@Html.ActionLink("Form Test", "FormTest")
@Html.ActionLink("Form Test", "FormTest")<br />
<a href="http://google.com/">Unsafe Url</a><br />
</p>

0 comments on commit 3c8fcd5

Please sign in to comment.