SEBWIN-314: Completed infrastructure for browser request filtering.

This commit is contained in:
dbuechel 2019-09-13 09:17:14 +02:00
parent d51422e188
commit 5209103c97
25 changed files with 416 additions and 165 deletions

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Settings.Browser;
namespace SafeExamBrowser.Browser.Contracts.Filters
{
/// <summary>
/// Defines the filter for browser requests.
/// </summary>
public interface IRequestFilter
{
/// <summary>
/// The default result to be returned by <see cref="Process(Request)"/> if no rule matches.
/// </summary>
FilterResult Default { get; set; }
/// <summary>
/// Loads the given filter rule to be used when processing requests.
/// </summary>
void Load(IRule rule);
/// <summary>
/// Filters the given request according to the loaded rules.
/// </summary>
FilterResult Process(Request request);
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Settings.Browser;
namespace SafeExamBrowser.Browser.Contracts.Filters
{
/// <summary>
/// Defines a request filter rule.
/// </summary>
public interface IRule
{
/// <summary>
/// The filter result to be used if the rule matches a request.
/// </summary>
FilterResult Result { get; }
/// <summary>
/// Initializes the rule for processing requests.
/// </summary>
void Initialize(FilterRuleSettings settings);
/// <summary>
/// Indicates whether the rule applies for the given request.
/// </summary>
bool IsMatch(Request request);
}
}

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Settings.Browser;
namespace SafeExamBrowser.Browser.Contracts.Filters
{
/// <summary>
/// Builds request filter rules.
/// </summary>
public interface IRuleFactory
{
/// <summary>
/// Creates a filter rule for the given type.
/// </summary>
IRule CreateRule(FilterRuleType type);
}
}

View file

@ -6,16 +6,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
namespace SafeExamBrowser.Browser.Filters.Rules namespace SafeExamBrowser.Browser.Contracts.Filters
{ {
internal abstract class Rule /// <summary>
/// Holds data relevant for filtering requests.
/// </summary>
public class Request
{ {
internal Rule(string expression) /// <summary>
{ /// The full URL of the request.
Initialize(expression); /// </summary>
} public string Url { get; set; }
internal abstract bool IsMatch(string url);
protected abstract void Initialize(string expression);
} }
} }

View file

@ -56,6 +56,10 @@
<Compile Include="Events\DownloadEventArgs.cs" /> <Compile Include="Events\DownloadEventArgs.cs" />
<Compile Include="Events\DownloadFinishedCallback.cs" /> <Compile Include="Events\DownloadFinishedCallback.cs" />
<Compile Include="Events\DownloadRequestedEventHandler.cs" /> <Compile Include="Events\DownloadRequestedEventHandler.cs" />
<Compile Include="Filters\IRequestFilter.cs" />
<Compile Include="Filters\IRule.cs" />
<Compile Include="Filters\IRuleFactory.cs" />
<Compile Include="Filters\Request.cs" />
<Compile Include="IBrowserApplication.cs" /> <Compile Include="IBrowserApplication.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
@ -64,6 +68,10 @@
<Project>{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}</Project> <Project>{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}</Project>
<Name>SafeExamBrowser.Applications.Contracts</Name> <Name>SafeExamBrowser.Applications.Contracts</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
<Name>SafeExamBrowser.Settings</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View file

@ -8,6 +8,8 @@
using System; using System;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Filters; using SafeExamBrowser.Browser.Filters;
using SafeExamBrowser.Settings.Browser; using SafeExamBrowser.Settings.Browser;
@ -27,13 +29,20 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters
[TestMethod] [TestMethod]
public void MustProcessBlockRulesFirst() public void MustProcessBlockRulesFirst()
{ {
var allow = new FilterRuleSettings { Expression = "*", Type = FilterType.Simplified, Result = FilterResult.Allow }; var allow = new Mock<IRule>();
var block = new FilterRuleSettings { Expression = "*", Type = FilterType.Simplified, Result = FilterResult.Block }; var block = new Mock<IRule>();
sut.Load(allow); allow.SetupGet(r => r.Result).Returns(FilterResult.Allow);
sut.Load(block); block.SetupGet(r => r.Result).Returns(FilterResult.Block);
block.Setup(r => r.IsMatch(It.IsAny<Request>())).Returns(true);
var result = sut.Process("safeexambrowser.org"); sut.Load(allow.Object);
sut.Load(block.Object);
var result = sut.Process(new Request());
allow.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Never);
block.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Once);
Assert.AreEqual(FilterResult.Block, result); Assert.AreEqual(FilterResult.Block, result);
} }
@ -41,28 +50,41 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters
[TestMethod] [TestMethod]
public void MustProcessAllowRulesSecond() public void MustProcessAllowRulesSecond()
{ {
var allow = new FilterRuleSettings { Expression = "*", Type = FilterType.Simplified, Result = FilterResult.Allow }; var allow = new Mock<IRule>();
var block = new FilterRuleSettings { Expression = "xyz", Type = FilterType.Simplified, Result = FilterResult.Block }; var block = new Mock<IRule>();
sut.Load(allow); allow.SetupGet(r => r.Result).Returns(FilterResult.Allow);
sut.Load(block); allow.Setup(r => r.IsMatch(It.IsAny<Request>())).Returns(true);
block.SetupGet(r => r.Result).Returns(FilterResult.Block);
var result = sut.Process("safeexambrowser.org"); sut.Load(allow.Object);
sut.Load(block.Object);
var result = sut.Process(new Request());
allow.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Once);
block.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Once);
Assert.AreEqual(FilterResult.Allow, result); Assert.AreEqual(FilterResult.Allow, result);
} }
[TestMethod] [TestMethod]
public void MustReturnDefault() public void MustReturnDefaultWithoutMatch()
{ {
var allow = new FilterRuleSettings { Expression = "xyz", Type = FilterType.Simplified, Result = FilterResult.Allow }; var allow = new Mock<IRule>();
var block = new FilterRuleSettings { Expression = "xyz", Type = FilterType.Simplified, Result = FilterResult.Block }; var block = new Mock<IRule>();
allow.SetupGet(r => r.Result).Returns(FilterResult.Allow);
block.SetupGet(r => r.Result).Returns(FilterResult.Block);
sut.Default = (FilterResult) (-1); sut.Default = (FilterResult) (-1);
sut.Load(allow); sut.Load(allow.Object);
sut.Load(block); sut.Load(block.Object);
var result = sut.Process("safeexambrowser.org"); var result = sut.Process(new Request());
allow.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Once);
block.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Once);
Assert.AreEqual((FilterResult) (-1), result); Assert.AreEqual((FilterResult) (-1), result);
} }
@ -71,11 +93,11 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters
public void MustReturnDefaultWithoutRules() public void MustReturnDefaultWithoutRules()
{ {
sut.Default = FilterResult.Allow; sut.Default = FilterResult.Allow;
var result = sut.Process("safeexambrowser.org"); var result = sut.Process(new Request());
Assert.AreEqual(FilterResult.Allow, result); Assert.AreEqual(FilterResult.Allow, result);
sut.Default = FilterResult.Block; sut.Default = FilterResult.Block;
result = sut.Process("safeexambrowser.org"); result = sut.Process(new Request());
Assert.AreEqual(FilterResult.Block, result); Assert.AreEqual(FilterResult.Block, result);
} }
@ -83,14 +105,10 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters
[ExpectedException(typeof(NotImplementedException))] [ExpectedException(typeof(NotImplementedException))]
public void MustNotAllowUnsupportedResult() public void MustNotAllowUnsupportedResult()
{ {
sut.Load(new FilterRuleSettings { Result = (FilterResult) (-1) }); var rule = new Mock<IRule>();
}
[TestMethod] rule.SetupGet(r => r.Result).Returns((FilterResult) (-1));
[ExpectedException(typeof(NotImplementedException))] sut.Load(rule.Object);
public void MustNotAllowUnsupportedFilterType()
{
sut.Load(new FilterRuleSettings { Type = (FilterType) (-1) });
} }
} }
} }

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SafeExamBrowser.Browser.Filters;
using SafeExamBrowser.Browser.Filters.Rules;
using SafeExamBrowser.Settings.Browser;
namespace SafeExamBrowser.Browser.UnitTests.Filters
{
[TestClass]
public class RuleFactoryTests
{
private RuleFactory sut;
[TestInitialize]
public void Initialize()
{
sut = new RuleFactory();
}
[TestMethod]
public void MustCreateCorrectRules()
{
Assert.IsInstanceOfType(sut.CreateRule(FilterRuleType.Regex), typeof(RegexRule));
Assert.IsInstanceOfType(sut.CreateRule(FilterRuleType.Simplified), typeof(SimplifiedRule));
}
[TestMethod]
[ExpectedException(typeof(NotImplementedException))]
public void MustNotAllowUnsupportedFilterType()
{
sut.CreateRule((FilterRuleType) (-1));
}
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SafeExamBrowser.Browser.Filters.Rules;
namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
{
[TestClass]
public class RegexRuleTests
{
private RegexRule sut;
[TestInitialize]
public void Initialize()
{
sut = new RegexRule();
}
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SafeExamBrowser.Browser.Filters.Rules;
namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
{
[TestClass]
public class SimplifiedRuleTests
{
private SimplifiedRule sut;
[TestInitialize]
public void Initialize()
{
sut = new SimplifiedRule();
}
}
}

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\MSTest.TestAdapter.1.3.2\build\net45\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.1.3.2\build\net45\MSTest.TestAdapter.props')" /> <Import Project="..\packages\MSTest.TestAdapter.2.0.0\build\net45\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.2.0.0\build\net45\MSTest.TestAdapter.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@ -57,20 +57,37 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\packages\Castle.Core.4.4.0\lib\net45\Castle.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath> <HintPath>..\packages\MSTest.TestFramework.2.0.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
</Reference> </Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> <Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath> <HintPath>..\packages\MSTest.TestFramework.2.0.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
</Reference>
<Reference Include="Moq, Version=4.13.0.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.13.0\lib\net45\Moq.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.3\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Filters\RequestFilterTests.cs" /> <Compile Include="Filters\RequestFilterTests.cs" />
<Compile Include="Filters\RuleFactoryTests.cs" />
<Compile Include="Filters\Rules\RegexRuleTests.cs" />
<Compile Include="Filters\Rules\SimplifiedRuleTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" />
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -93,8 +110,8 @@
<PropertyGroup> <PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup> </PropertyGroup>
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.1.3.2\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.1.3.2\build\net45\MSTest.TestAdapter.props'))" /> <Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.0.0\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.0.0\build\net45\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.1.3.2\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.1.3.2\build\net45\MSTest.TestAdapter.targets'))" /> <Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.0.0\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.0.0\build\net45\MSTest.TestAdapter.targets'))" />
</Target> </Target>
<Import Project="..\packages\MSTest.TestAdapter.1.3.2\build\net45\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.1.3.2\build\net45\MSTest.TestAdapter.targets')" /> <Import Project="..\packages\MSTest.TestAdapter.2.0.0\build\net45\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.2.0.0\build\net45\MSTest.TestAdapter.targets')" />
</Project> </Project>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View file

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="MSTest.TestAdapter" version="1.3.2" targetFramework="net472" /> <package id="Castle.Core" version="4.4.0" targetFramework="net472" />
<package id="MSTest.TestFramework" version="1.3.2" targetFramework="net472" /> <package id="Moq" version="4.13.0" targetFramework="net472" />
<package id="MSTest.TestAdapter" version="2.0.0" targetFramework="net472" />
<package id="MSTest.TestFramework" version="2.0.0" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.3" targetFramework="net472" />
</packages> </packages>

View file

@ -13,6 +13,7 @@ using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Applications.Contracts.Events; using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Browser.Contracts.Events; using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Browser.Events; using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Browser.Filters;
using SafeExamBrowser.Browser.Handlers; using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.Configuration.Contracts; using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
@ -102,8 +103,9 @@ namespace SafeExamBrowser.Browser
var downloadHandler = new DownloadHandler(appConfig, settings, downloadLogger); var downloadHandler = new DownloadHandler(appConfig, settings, downloadLogger);
var keyboardHandler = new KeyboardHandler(); var keyboardHandler = new KeyboardHandler();
var lifeSpanHandler = new LifeSpanHandler(); var lifeSpanHandler = new LifeSpanHandler();
var requestFilter = new RequestFilter();
var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} {Id}"); var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} {Id}");
var requestHandler = new RequestHandler(appConfig, settings.Filter, requestLogger, text); var requestHandler = new RequestHandler(appConfig, settings.Filter, requestFilter, requestLogger, text);
displayHandler.FaviconChanged += DisplayHandler_FaviconChanged; displayHandler.FaviconChanged += DisplayHandler_FaviconChanged;
displayHandler.ProgressChanged += DisplayHandler_ProgressChanged; displayHandler.ProgressChanged += DisplayHandler_ProgressChanged;
@ -115,14 +117,27 @@ namespace SafeExamBrowser.Browser
lifeSpanHandler.PopupRequested += LifeSpanHandler_PopupRequested; lifeSpanHandler.PopupRequested += LifeSpanHandler_PopupRequested;
requestHandler.RequestBlocked += RequestHandler_RequestBlocked; requestHandler.RequestBlocked += RequestHandler_RequestBlocked;
if (settings.Filter.ProcessContentRequests || settings.Filter.ProcessMainRequests)
{
var factory = new RuleFactory();
foreach (var settings in settings.Filter.Rules)
{
var rule = factory.CreateRule(settings.Type);
rule.Initialize(settings);
requestFilter.Load(rule);
}
logger.Debug($"Initialized request filter with {settings.Filter.Rules.Count} rule(s).");
}
control = new BrowserControl(contextMenuHandler, displayHandler, downloadHandler, keyboardHandler, lifeSpanHandler, requestHandler, url); control = new BrowserControl(contextMenuHandler, displayHandler, downloadHandler, keyboardHandler, lifeSpanHandler, requestHandler, url);
control.AddressChanged += Control_AddressChanged; control.AddressChanged += Control_AddressChanged;
control.LoadingStateChanged += Control_LoadingStateChanged; control.LoadingStateChanged += Control_LoadingStateChanged;
control.TitleChanged += Control_TitleChanged; control.TitleChanged += Control_TitleChanged;
requestHandler.Initiailize();
control.Initialize(); control.Initialize();
logger.Debug("Initialized browser control."); logger.Debug("Initialized browser control.");
} }

View file

@ -8,42 +8,28 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using SafeExamBrowser.Browser.Filters.Rules; using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Settings.Browser; using SafeExamBrowser.Settings.Browser;
namespace SafeExamBrowser.Browser.Filters namespace SafeExamBrowser.Browser.Filters
{ {
internal class RequestFilter internal class RequestFilter : IRequestFilter
{ {
private IList<Rule> allowRules; private IList<IRule> allowRules;
private IList<Rule> blockRules; private IList<IRule> blockRules;
internal FilterResult Default { get; set; } public FilterResult Default { get; set; }
internal RequestFilter() internal RequestFilter()
{ {
allowRules = new List<Rule>(); allowRules = new List<IRule>();
blockRules = new List<Rule>(); blockRules = new List<IRule>();
Default = FilterResult.Block; Default = FilterResult.Block;
} }
internal void Load(FilterRuleSettings settings) public void Load(IRule rule)
{ {
var rule = default(Rule); switch (rule.Result)
switch (settings.Type)
{
case FilterType.Regex:
rule = new RegexRule(settings.Expression);
break;
case FilterType.Simplified:
rule = new SimpleRule(settings.Expression);
break;
default:
throw new NotImplementedException($"Filter rule of type '{settings.Type}' is not yet implemented!");
}
switch (settings.Result)
{ {
case FilterResult.Allow: case FilterResult.Allow:
allowRules.Add(rule); allowRules.Add(rule);
@ -52,15 +38,15 @@ namespace SafeExamBrowser.Browser.Filters
blockRules.Add(rule); blockRules.Add(rule);
break; break;
default: default:
throw new NotImplementedException($"Filter result '{settings.Result}' is not yet implemented!"); throw new NotImplementedException($"Filter processing for result '{rule.Result}' is not yet implemented!");
} }
} }
internal FilterResult Process(string url) public FilterResult Process(Request request)
{ {
foreach (var rule in blockRules) foreach (var rule in blockRules)
{ {
if (rule.IsMatch(url)) if (rule.IsMatch(request))
{ {
return FilterResult.Block; return FilterResult.Block;
} }
@ -68,7 +54,7 @@ namespace SafeExamBrowser.Browser.Filters
foreach (var rule in allowRules) foreach (var rule in allowRules)
{ {
if (rule.IsMatch(url)) if (rule.IsMatch(request))
{ {
return FilterResult.Allow; return FilterResult.Allow;
} }

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Filters.Rules;
using SafeExamBrowser.Settings.Browser;
namespace SafeExamBrowser.Browser.Filters
{
internal class RuleFactory : IRuleFactory
{
public IRule CreateRule(FilterRuleType type)
{
switch (type)
{
case FilterRuleType.Regex:
return new RegexRule();
case FilterRuleType.Simplified:
return new SimplifiedRule();
default:
throw new NotImplementedException($"Filter rule of type '{type}' is not yet implemented!");
}
}
}
}

View file

@ -7,25 +7,26 @@
*/ */
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Settings.Browser;
namespace SafeExamBrowser.Browser.Filters.Rules namespace SafeExamBrowser.Browser.Filters.Rules
{ {
internal class RegexRule : Rule internal class RegexRule : IRule
{ {
private string expression; private string expression;
public RegexRule(string expression) : base(expression) public FilterResult Result { get; private set; }
public void Initialize(FilterRuleSettings settings)
{ {
expression = settings.Expression;
Result = settings.Result;
} }
protected override void Initialize(string expression) public bool IsMatch(Request request)
{ {
this.expression = expression; return Regex.IsMatch(request.Url, expression, RegexOptions.IgnoreCase);
}
internal override bool IsMatch(string url)
{
return Regex.IsMatch(url, expression, RegexOptions.IgnoreCase);
} }
} }
} }

View file

@ -7,25 +7,26 @@
*/ */
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Settings.Browser;
namespace SafeExamBrowser.Browser.Filters.Rules namespace SafeExamBrowser.Browser.Filters.Rules
{ {
internal class SimpleRule : Rule internal class SimplifiedRule : IRule
{ {
private string expression; private string expression;
public SimpleRule(string expression) : base(expression) public FilterResult Result { get; private set; }
public void Initialize(FilterRuleSettings settings)
{ {
expression = settings.Expression.Replace("*", @".*");
Result = settings.Result;
} }
protected override void Initialize(string expression) public bool IsMatch(Request request)
{ {
this.expression = expression.Replace("*", @".*"); return Regex.IsMatch(request.Url, expression, RegexOptions.IgnoreCase);
}
internal override bool IsMatch(string url)
{
return Regex.IsMatch(url, expression, RegexOptions.IgnoreCase);
} }
} }
} }

View file

@ -7,8 +7,8 @@
*/ */
using CefSharp; using CefSharp;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Events; using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Browser.Filters;
using SafeExamBrowser.Configuration.Contracts; using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
@ -18,31 +18,21 @@ namespace SafeExamBrowser.Browser.Handlers
{ {
internal class RequestHandler : CefSharp.Handler.RequestHandler internal class RequestHandler : CefSharp.Handler.RequestHandler
{ {
private RequestFilter filter; private IRequestFilter filter;
private ILogger logger; private ILogger logger;
private ResourceHandler resourceHandler; private ResourceHandler resourceHandler;
private BrowserFilterSettings settings; private BrowserFilterSettings settings;
internal event RequestBlockedEventHandler RequestBlocked; internal event RequestBlockedEventHandler RequestBlocked;
internal RequestHandler(AppConfig appConfig, BrowserFilterSettings settings, ILogger logger, IText text) internal RequestHandler(AppConfig appConfig, BrowserFilterSettings settings, IRequestFilter filter, ILogger logger, IText text)
{ {
this.filter = new RequestFilter(); this.filter = filter;
this.logger = logger; this.logger = logger;
this.resourceHandler = new ResourceHandler(appConfig, settings, filter, logger, text); this.resourceHandler = new ResourceHandler(appConfig, settings, filter, logger, text);
this.settings = settings; this.settings = settings;
} }
internal void Initiailize()
{
if (settings.FilterMainRequests || settings.FilterContentRequests)
{
InitializeFilter();
}
resourceHandler.Initialize();
}
protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling) protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
{ {
return resourceHandler; return resourceHandler;
@ -62,9 +52,9 @@ namespace SafeExamBrowser.Browser.Handlers
private bool Block(IRequest request) private bool Block(IRequest request)
{ {
if (settings.FilterMainRequests) if (settings.ProcessMainRequests)
{ {
var result = filter.Process(request.Url); var result = filter.Process(new Request { Url = request.Url });
var block = result == FilterResult.Block; var block = result == FilterResult.Block;
if (block) if (block)
@ -77,15 +67,5 @@ namespace SafeExamBrowser.Browser.Handlers
return false; return false;
} }
private void InitializeFilter()
{
foreach (var rule in settings.Rules)
{
filter.Load(rule);
}
logger.Debug($"Initialized request filter with {settings.Rules.Count} rules.");
}
} }
} }

View file

@ -11,6 +11,7 @@ using System.Collections.Specialized;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using CefSharp; using CefSharp;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Filters; using SafeExamBrowser.Browser.Filters;
using SafeExamBrowser.Configuration.Contracts; using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
@ -24,12 +25,12 @@ namespace SafeExamBrowser.Browser.Handlers
private AppConfig appConfig; private AppConfig appConfig;
private BrowserFilterSettings settings; private BrowserFilterSettings settings;
private ILogger logger; private ILogger logger;
private RequestFilter filter; private IRequestFilter filter;
private IResourceHandler contentHandler; private IResourceHandler contentHandler;
private IResourceHandler pageHandler; private IResourceHandler pageHandler;
private IText text; private IText text;
internal ResourceHandler(AppConfig appConfig, BrowserFilterSettings settings, RequestFilter filter, ILogger logger, IText text) internal ResourceHandler(AppConfig appConfig, BrowserFilterSettings settings, IRequestFilter filter, ILogger logger, IText text)
{ {
this.appConfig = appConfig; this.appConfig = appConfig;
this.filter = filter; this.filter = filter;
@ -38,14 +39,6 @@ namespace SafeExamBrowser.Browser.Handlers
this.text = text; this.text = text;
} }
internal void Initialize()
{
if (settings.FilterContentRequests)
{
InitializeResourceHandlers();
}
}
protected override IResourceHandler GetResourceHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request) protected override IResourceHandler GetResourceHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request)
{ {
if (Block(request)) if (Block(request))
@ -84,9 +77,9 @@ namespace SafeExamBrowser.Browser.Handlers
private bool Block(IRequest request) private bool Block(IRequest request)
{ {
if (settings.FilterContentRequests) if (settings.ProcessContentRequests)
{ {
var result = filter.Process(request.Url); var result = filter.Process(new Request { Url = request.Url });
var block = result == FilterResult.Block; var block = result == FilterResult.Block;
if (block) if (block)
@ -100,27 +93,6 @@ namespace SafeExamBrowser.Browser.Handlers
return false; return false;
} }
private void InitializeResourceHandlers()
{
var assembly = Assembly.GetAssembly(typeof(RequestFilter));
var contentMessage = text.Get(TextKey.Browser_BlockedContentMessage);
var contentStream = assembly.GetManifestResourceStream($"{typeof(RequestFilter).Namespace}.BlockedContent.html");
var pageButton = text.Get(TextKey.Browser_BlockedPageButton);
var pageMessage = text.Get(TextKey.Browser_BlockedPageMessage);
var pageTitle = text.Get(TextKey.Browser_BlockedPageTitle);
var pageStream = assembly.GetManifestResourceStream($"{typeof(RequestFilter).Namespace}.BlockedPage.html");
var contentHtml = new StreamReader(contentStream).ReadToEnd();
var pageHtml = new StreamReader(pageStream).ReadToEnd();
contentHtml = contentHtml.Replace("%%MESSAGE%%", contentMessage);
contentHandler = CefSharp.ResourceHandler.FromString(contentHtml);
pageHtml = pageHtml.Replace("%%MESSAGE%%", pageMessage).Replace("%%TITLE%%", pageTitle).Replace("%%BACK_BUTTON%%", pageButton);
pageHandler = CefSharp.ResourceHandler.FromString(pageHtml);
logger.Debug("Initialized resource handlers for blocked requests.");
}
private bool IsMailtoUrl(string url) private bool IsMailtoUrl(string url)
{ {
return url.StartsWith(Uri.UriSchemeMailto); return url.StartsWith(Uri.UriSchemeMailto);
@ -145,6 +117,11 @@ namespace SafeExamBrowser.Browser.Handlers
private IResourceHandler ResourceHandlerFor(ResourceType resourceType) private IResourceHandler ResourceHandlerFor(ResourceType resourceType)
{ {
if (contentHandler == default(IResourceHandler) || pageHandler == default(IResourceHandler))
{
InitializeResourceHandlers();
}
switch (resourceType) switch (resourceType)
{ {
case ResourceType.MainFrame: case ResourceType.MainFrame:
@ -154,5 +131,26 @@ namespace SafeExamBrowser.Browser.Handlers
return contentHandler; return contentHandler;
} }
} }
private void InitializeResourceHandlers()
{
var assembly = Assembly.GetAssembly(typeof(RequestFilter));
var contentMessage = text.Get(TextKey.Browser_BlockedContentMessage);
var contentStream = assembly.GetManifestResourceStream($"{typeof(RequestFilter).Namespace}.BlockedContent.html");
var pageButton = text.Get(TextKey.Browser_BlockedPageButton);
var pageMessage = text.Get(TextKey.Browser_BlockedPageMessage);
var pageTitle = text.Get(TextKey.Browser_BlockedPageTitle);
var pageStream = assembly.GetManifestResourceStream($"{typeof(RequestFilter).Namespace}.BlockedPage.html");
var contentHtml = new StreamReader(contentStream).ReadToEnd();
var pageHtml = new StreamReader(pageStream).ReadToEnd();
contentHtml = contentHtml.Replace("%%MESSAGE%%", contentMessage);
contentHandler = CefSharp.ResourceHandler.FromString(contentHtml);
pageHtml = pageHtml.Replace("%%MESSAGE%%", pageMessage).Replace("%%TITLE%%", pageTitle).Replace("%%BACK_BUTTON%%", pageButton);
pageHandler = CefSharp.ResourceHandler.FromString(pageHtml);
logger.Debug("Initialized resource handlers for blocked requests.");
}
} }
} }

View file

@ -75,9 +75,9 @@
<Compile Include="Events\ProgressChangedEventHandler.cs" /> <Compile Include="Events\ProgressChangedEventHandler.cs" />
<Compile Include="Events\RequestBlockedEventHandler.cs" /> <Compile Include="Events\RequestBlockedEventHandler.cs" />
<Compile Include="Filters\RequestFilter.cs" /> <Compile Include="Filters\RequestFilter.cs" />
<Compile Include="Filters\Rules\Rule.cs" />
<Compile Include="Filters\Rules\RegexRule.cs" /> <Compile Include="Filters\Rules\RegexRule.cs" />
<Compile Include="Filters\Rules\SimpleRule.cs" /> <Compile Include="Filters\RuleFactory.cs" />
<Compile Include="Filters\Rules\SimplifiedRule.cs" />
<Compile Include="Handlers\ContextMenuHandler.cs" /> <Compile Include="Handlers\ContextMenuHandler.cs" />
<Compile Include="BrowserControl.cs"> <Compile Include="BrowserControl.cs">
<SubType>Component</SubType> <SubType>Component</SubType>

View file

@ -108,17 +108,17 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
private void MapEnableContentRequestFilter(ApplicationSettings settings, object value) private void MapEnableContentRequestFilter(ApplicationSettings settings, object value)
{ {
if (value is bool filter) if (value is bool process)
{ {
settings.Browser.Filter.FilterContentRequests = filter; settings.Browser.Filter.ProcessContentRequests = process;
} }
} }
private void MapEnableMainRequestFilter(ApplicationSettings settings, object value) private void MapEnableMainRequestFilter(ApplicationSettings settings, object value)
{ {
if (value is bool filter) if (value is bool process)
{ {
settings.Browser.Filter.FilterMainRequests = filter; settings.Browser.Filter.ProcessMainRequests = process;
} }
} }
@ -148,7 +148,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
if (ruleData.TryGetValue(Keys.Browser.Filter.RuleExpressionIsRegex, out v) && v is bool regex) if (ruleData.TryGetValue(Keys.Browser.Filter.RuleExpressionIsRegex, out v) && v is bool regex)
{ {
rule.Type = regex ? FilterType.Regex : FilterType.Simplified; rule.Type = regex ? FilterRuleType.Regex : FilterRuleType.Simplified;
} }
settings.Browser.Filter.Rules.Add(rule); settings.Browser.Filter.Rules.Add(rule);

View file

@ -18,14 +18,14 @@ namespace SafeExamBrowser.Settings.Browser
public class BrowserFilterSettings public class BrowserFilterSettings
{ {
/// <summary> /// <summary>
/// Defines whether all content requests for a web page should be filtered according to the defined <see cref="Rules"/>. /// Defines whether content requests for a web page should be filtered according to the defined <see cref="Rules"/>.
/// </summary> /// </summary>
public bool FilterContentRequests { get; set; } public bool ProcessContentRequests { get; set; }
/// <summary> /// <summary>
/// Defines whether the main request for a web page should be filtered according to the defined <see cref="Rules"/>. /// Defines whether the main request for a web page should be filtered according to the defined <see cref="Rules"/>.
/// </summary> /// </summary>
public bool FilterMainRequests { get; set; } public bool ProcessMainRequests { get; set; }
/// <summary> /// <summary>
/// Defines all rules to be used to filter web requests. /// Defines all rules to be used to filter web requests.

View file

@ -29,6 +29,6 @@ namespace SafeExamBrowser.Settings.Browser
/// <summary> /// <summary>
/// The filter type which defines how the <see cref="Expression"/> is processed. /// The filter type which defines how the <see cref="Expression"/> is processed.
/// </summary> /// </summary>
public FilterType Type { get; set; } public FilterRuleType Type { get; set; }
} }
} }

View file

@ -9,17 +9,17 @@
namespace SafeExamBrowser.Settings.Browser namespace SafeExamBrowser.Settings.Browser
{ {
/// <summary> /// <summary>
/// Defines all possible request filter types. /// Defines all possible filter rule types.
/// </summary> /// </summary>
public enum FilterType public enum FilterRuleType
{ {
/// <summary> /// <summary>
/// The filter is based on a regular expression. /// The filter rule is based on a regular expression.
/// </summary> /// </summary>
Regex, Regex,
/// <summary> /// <summary>
/// The filter is based on a simplified expression with wildcards. /// The filter rule is based on a simplified expression with wildcards.
/// </summary> /// </summary>
Simplified Simplified
} }

View file

@ -58,7 +58,7 @@
<Compile Include="Browser\BrowserWindowSettings.cs" /> <Compile Include="Browser\BrowserWindowSettings.cs" />
<Compile Include="Browser\FilterResult.cs" /> <Compile Include="Browser\FilterResult.cs" />
<Compile Include="Browser\FilterRuleSettings.cs" /> <Compile Include="Browser\FilterRuleSettings.cs" />
<Compile Include="Browser\FilterType.cs" /> <Compile Include="Browser\FilterRuleType.cs" />
<Compile Include="ConfigurationMode.cs" /> <Compile Include="ConfigurationMode.cs" />
<Compile Include="KioskMode.cs" /> <Compile Include="KioskMode.cs" />
<Compile Include="Logging\LogLevel.cs" /> <Compile Include="Logging\LogLevel.cs" />