SEBWIN-315: Implemented basic proxy support.

This commit is contained in:
dbuechel 2019-12-18 08:09:59 +01:00
parent 8953313642
commit 5b3a2a3861
13 changed files with 514 additions and 18 deletions

View file

@ -20,6 +20,7 @@ using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.Settings.Logging;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
@ -145,6 +146,8 @@ namespace SafeExamBrowser.Browser
UserAgent = InitializeUserAgent()
};
InitializeProxySettings(cefSettings);
cefSettings.CefCommandLineArgs.Add("touch-events", "enabled");
logger.Debug($"Cache path: {cefSettings.CachePath}");
@ -155,6 +158,56 @@ namespace SafeExamBrowser.Browser
return cefSettings;
}
private void InitializeProxySettings(CefSettings cefSettings)
{
if (settings.Proxy.Policy == ProxyPolicy.Custom)
{
if (settings.Proxy.AutoConfigure)
{
cefSettings.CefCommandLineArgs.Add("proxy-pac-url", settings.Proxy.AutoConfigureUrl);
}
if (settings.Proxy.AutoDetect)
{
cefSettings.CefCommandLineArgs.Add("proxy-auto-detect", "");
}
if (settings.Proxy.BypassList.Any())
{
cefSettings.CefCommandLineArgs.Add("proxy-bypass-list", string.Join(";", settings.Proxy.BypassList));
}
if (settings.Proxy.Proxies.Any())
{
var proxies = new List<string>();
foreach (var proxy in settings.Proxy.Proxies)
{
proxies.Add($"{ToScheme(proxy.Protocol)}={proxy.Host}:{proxy.Port}");
}
cefSettings.CefCommandLineArgs.Add("proxy-server", string.Join(";", proxies));
}
}
}
private string ToScheme(ProxyProtocol protocol)
{
switch (protocol)
{
case ProxyProtocol.Ftp:
return Uri.UriSchemeFtp;
case ProxyProtocol.Http:
return Uri.UriSchemeHttp;
case ProxyProtocol.Https:
return Uri.UriSchemeHttps;
case ProxyProtocol.Socks:
return "socks";
}
throw new NotImplementedException($"Mapping for proxy protocol '{protocol}' is not yet implemented!");
}
private void Instance_PopupRequested(PopupRequestedEventArgs args)
{
logger.Info($"Received request to create new instance for '{args.Url}'...");

View file

@ -111,7 +111,7 @@ namespace SafeExamBrowser.Browser
var lifeSpanHandler = new LifeSpanHandler();
var requestFilter = new RequestFilter();
var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} {Id}");
var requestHandler = new RequestHandler(appConfig, settings.Filter, requestFilter, requestLogger, text);
var requestHandler = new RequestHandler(appConfig, requestFilter, requestLogger, settings, text);
Icon = new BrowserIconResource();

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using CefSharp;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Events;
@ -13,6 +14,7 @@ using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser;
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
namespace SafeExamBrowser.Browser.Handlers
{
@ -21,24 +23,42 @@ namespace SafeExamBrowser.Browser.Handlers
private IRequestFilter filter;
private ILogger logger;
private ResourceHandler resourceHandler;
private BrowserFilterSettings settings;
private BrowserSettings settings;
internal event RequestBlockedEventHandler RequestBlocked;
internal RequestHandler(AppConfig appConfig, BrowserFilterSettings settings, IRequestFilter filter, ILogger logger, IText text)
internal RequestHandler(AppConfig appConfig, IRequestFilter filter, ILogger logger, BrowserSettings settings, IText text)
{
this.filter = filter;
this.logger = logger;
this.resourceHandler = new ResourceHandler(appConfig, settings, filter, logger, text);
this.settings = settings;
this.resourceHandler = new ResourceHandler(appConfig, settings.Filter, filter, logger, text);
}
protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
protected override bool GetAuthCredentials(IWebBrowser webBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback)
{
if (isProxy)
{
foreach (var proxy in settings.Proxy.Proxies)
{
if (proxy.RequiresAuthentication && host?.Equals(proxy.Host, StringComparison.OrdinalIgnoreCase) == true && port == proxy.Port)
{
callback.Continue(proxy.Username, proxy.Password);
return true;
}
}
}
return base.GetAuthCredentials(webBrowser, browser, originUrl, isProxy, host, port, realm, scheme, callback);
}
protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
{
return resourceHandler;
}
protected override bool OnBeforeBrowse(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect)
protected override bool OnBeforeBrowse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect)
{
if (Block(request))
{
@ -47,12 +67,12 @@ namespace SafeExamBrowser.Browser.Handlers
return true;
}
return base.OnBeforeBrowse(chromiumWebBrowser, browser, frame, request, userGesture, isRedirect);
return base.OnBeforeBrowse(webBrowser, browser, frame, request, userGesture, isRedirect);
}
private bool Block(IRequest request)
{
if (settings.ProcessMainRequests)
if (settings.Filter.ProcessMainRequests)
{
var result = filter.Process(new Request { Url = request.Url });
var block = result == FilterResult.Block;

View file

@ -175,7 +175,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
}
}
private void MapUrlFilterRules(AppSettings settings, object value)
private void MapFilterRules(AppSettings settings, object value)
{
const int ALLOW = 1;
@ -213,6 +213,210 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
}
}
private void MapProxySettings(AppSettings settings, object value)
{
if (value is IDictionary<string, object> data)
{
if (data.TryGetValue(Keys.Browser.Proxy.AutoConfigure, out var v) && v is bool autoConfigure)
{
settings.Browser.Proxy.AutoConfigure = autoConfigure;
}
if (data.TryGetValue(Keys.Browser.Proxy.AutoConfigureUrl, out v) && v is string url)
{
settings.Browser.Proxy.AutoConfigureUrl = url;
}
if (data.TryGetValue(Keys.Browser.Proxy.AutoDetect, out v) && v is bool autoDetect)
{
settings.Browser.Proxy.AutoDetect = autoDetect;
}
if (data.TryGetValue(Keys.Browser.Proxy.BypassList, out v) && v is IList<object> list)
{
MapProxyBypassList(settings, list);
}
if (data.TryGetValue(Keys.Browser.Proxy.Ftp.Enable, out v) && v is bool ftpEnable && ftpEnable)
{
MapFtpProxy(settings, data);
}
if (data.TryGetValue(Keys.Browser.Proxy.Http.Enable, out v) && v is bool httpEnable && httpEnable)
{
MapHttpProxy(settings, data);
}
if (data.TryGetValue(Keys.Browser.Proxy.Https.Enable, out v) && v is bool httpsEnable && httpsEnable)
{
MapHttpsProxy(settings, data);
}
if (data.TryGetValue(Keys.Browser.Proxy.Socks.Enable, out v) && v is bool socksEnable && socksEnable)
{
MapSocksProxy(settings, data);
}
}
}
private void MapProxyBypassList(AppSettings settings, IList<object> bypassList)
{
foreach (var item in bypassList)
{
if (item is string host)
{
settings.Browser.Proxy.BypassList.Add(host);
}
}
}
private void MapFtpProxy(AppSettings settings, IDictionary<string, object> data)
{
var proxy = new ProxySettings { Protocol = ProxyProtocol.Ftp };
if (data.TryGetValue(Keys.Browser.Proxy.Ftp.Host, out var v) && v is string host)
{
proxy.Host = host;
}
if (data.TryGetValue(Keys.Browser.Proxy.Ftp.Password, out v) && v is string password)
{
proxy.Password = password;
}
if (data.TryGetValue(Keys.Browser.Proxy.Ftp.Port, out v) && v is int port)
{
proxy.Port = port;
}
if (data.TryGetValue(Keys.Browser.Proxy.Ftp.RequiresAuthentication, out v) && v is bool requiresAuthentication)
{
proxy.RequiresAuthentication = requiresAuthentication;
}
if (data.TryGetValue(Keys.Browser.Proxy.Ftp.Username, out v) && v is string username)
{
proxy.Username = username;
}
settings.Browser.Proxy.Proxies.Add(proxy);
}
private void MapHttpProxy(AppSettings settings, IDictionary<string, object> data)
{
var proxy = new ProxySettings { Protocol = ProxyProtocol.Http };
if (data.TryGetValue(Keys.Browser.Proxy.Http.Host, out var v) && v is string host)
{
proxy.Host = host;
}
if (data.TryGetValue(Keys.Browser.Proxy.Http.Password, out v) && v is string password)
{
proxy.Password = password;
}
if (data.TryGetValue(Keys.Browser.Proxy.Http.Port, out v) && v is int port)
{
proxy.Port = port;
}
if (data.TryGetValue(Keys.Browser.Proxy.Http.RequiresAuthentication, out v) && v is bool requiresAuthentication)
{
proxy.RequiresAuthentication = requiresAuthentication;
}
if (data.TryGetValue(Keys.Browser.Proxy.Http.Username, out v) && v is string username)
{
proxy.Username = username;
}
settings.Browser.Proxy.Proxies.Add(proxy);
}
private void MapHttpsProxy(AppSettings settings, IDictionary<string, object> data)
{
var proxy = new ProxySettings { Protocol = ProxyProtocol.Https };
if (data.TryGetValue(Keys.Browser.Proxy.Https.Host, out var v) && v is string host)
{
proxy.Host = host;
}
if (data.TryGetValue(Keys.Browser.Proxy.Https.Password, out v) && v is string password)
{
proxy.Password = password;
}
if (data.TryGetValue(Keys.Browser.Proxy.Https.Port, out v) && v is int port)
{
proxy.Port = port;
}
if (data.TryGetValue(Keys.Browser.Proxy.Https.RequiresAuthentication, out v) && v is bool requiresAuthentication)
{
proxy.RequiresAuthentication = requiresAuthentication;
}
if (data.TryGetValue(Keys.Browser.Proxy.Https.Username, out v) && v is string username)
{
proxy.Username = username;
}
settings.Browser.Proxy.Proxies.Add(proxy);
}
private void MapSocksProxy(AppSettings settings, IDictionary<string, object> data)
{
var proxy = new ProxySettings { Protocol = ProxyProtocol.Socks };
if (data.TryGetValue(Keys.Browser.Proxy.Socks.Host, out var v) && v is string host)
{
proxy.Host = host;
}
if (data.TryGetValue(Keys.Browser.Proxy.Socks.Password, out v) && v is string password)
{
proxy.Password = password;
}
if (data.TryGetValue(Keys.Browser.Proxy.Socks.Port, out v) && v is int port)
{
proxy.Port = port;
}
if (data.TryGetValue(Keys.Browser.Proxy.Socks.RequiresAuthentication, out v) && v is bool requiresAuthentication)
{
proxy.RequiresAuthentication = requiresAuthentication;
}
if (data.TryGetValue(Keys.Browser.Proxy.Socks.Username, out v) && v is string username)
{
proxy.Username = username;
}
settings.Browser.Proxy.Proxies.Add(proxy);
}
private void MapProxyPolicy(AppSettings settings, object value)
{
const int SYSTEM = 0;
const int CUSTOM = 1;
if (value is int policy)
{
switch (policy)
{
case CUSTOM:
settings.Browser.Proxy.Policy = ProxyPolicy.Custom;
break;
case SYSTEM:
settings.Browser.Proxy.Policy = ProxyPolicy.System;
break;
}
}
}
private void MapWindowHeightAdditionalWindow(AppSettings settings, object value)
{
if (value is string raw)

View file

@ -100,8 +100,8 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
case Keys.Browser.AdditionalWindow.WindowWidth:
MapWindowWidthAdditionalWindow(settings, value);
break;
case Keys.Browser.Filter.UrlFilterRules:
MapUrlFilterRules(settings, value);
case Keys.Browser.Filter.FilterRules:
MapFilterRules(settings, value);
break;
case Keys.Browser.MainWindow.AllowAddressBar:
MapAllowAddressBar(settings, value);
@ -127,6 +127,12 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
case Keys.Browser.MainWindow.WindowWidth:
MapWindowWidthMainWindow(settings, value);
break;
case Keys.Browser.Proxy.Policy:
MapProxyPolicy(settings, value);
break;
case Keys.Browser.Proxy.Settings:
MapProxySettings(settings, value);
break;
}
}

View file

@ -110,11 +110,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
settings.ActionCenter.ShowWirelessNetwork = false;
settings.ActionCenter.ShowClock = true;
settings.Browser.StartUrl = "https://www.safeexambrowser.org/start";
settings.Browser.AllowConfigurationDownloads = true;
settings.Browser.AllowDownloads = true;
settings.Browser.AllowPageZoom = true;
settings.Browser.PopupPolicy = PopupPolicy.Allow;
settings.Browser.AdditionalWindow.AllowAddressBar = false;
settings.Browser.AdditionalWindow.AllowBackwardNavigation = true;
settings.Browser.AdditionalWindow.AllowDeveloperConsole = false;
@ -125,6 +120,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
settings.Browser.AdditionalWindow.RelativeHeight = 100;
settings.Browser.AdditionalWindow.RelativeWidth = 50;
settings.Browser.AdditionalWindow.ShowReloadWarning = false;
settings.Browser.AllowConfigurationDownloads = true;
settings.Browser.AllowDownloads = true;
settings.Browser.AllowPageZoom = true;
settings.Browser.MainWindow.AllowAddressBar = false;
settings.Browser.MainWindow.AllowBackwardNavigation = false;
settings.Browser.MainWindow.AllowDeveloperConsole = false;
@ -134,6 +132,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
settings.Browser.MainWindow.RelativeHeight = 100;
settings.Browser.MainWindow.RelativeWidth = 100;
settings.Browser.MainWindow.ShowReloadWarning = true;
settings.Browser.PopupPolicy = PopupPolicy.Allow;
settings.Browser.Proxy.Policy = ProxyPolicy.System;
settings.Browser.StartUrl = "https://www.safeexambrowser.org/start";
settings.Keyboard.AllowAltEsc = false;
settings.Keyboard.AllowAltF4 = false;

View file

@ -71,11 +71,11 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
{
internal const string EnableContentRequestFilter = "URLFilterEnableContentFilter";
internal const string EnableMainRequestFilter = "URLFilterEnable";
internal const string FilterRules = "URLFilterRules";
internal const string RuleAction = "action";
internal const string RuleIsActive = "active";
internal const string RuleExpression = "expression";
internal const string RuleExpressionIsRegex = "regex";
internal const string UrlFilterRules = "URLFilterRules";
}
internal static class MainWindow
@ -89,6 +89,56 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal const string WindowWidth = "mainBrowserWindowWidth";
internal const string WindowPosition = "mainBrowserWindowPositioning";
}
internal static class Proxy
{
internal const string AutoConfigure = "AutoConfigurationEnabled";
internal const string AutoConfigureUrl = "AutoConfigurationURL";
internal const string AutoDetect = "AutoDiscoveryEnabled";
internal const string BypassList = "ExceptionsList";
internal const string Policy = "proxySettingsPolicy";
internal const string Settings = "proxies";
internal static class Ftp
{
internal const string Enable = "FTPEnable";
internal const string Host = "FTPProxy";
internal const string Password = "FTPPassword";
internal const string Port = "FTPPort";
internal const string RequiresAuthentication = "FTPRequiresPassword";
internal const string Username = "FTPUsername";
}
internal static class Http
{
internal const string Enable = "HTTPEnable";
internal const string Host = "HTTPProxy";
internal const string Password = "HTTPPassword";
internal const string Port = "HTTPPort";
internal const string RequiresAuthentication = "HTTPRequiresPassword";
internal const string Username = "HTTPUsername";
}
internal static class Https
{
internal const string Enable = "HTTPSEnable";
internal const string Host = "HTTPSProxy";
internal const string Password = "HTTPSPassword";
internal const string Port = "HTTPSPort";
internal const string RequiresAuthentication = "HTTPSRequiresPassword";
internal const string Username = "HTTPSUsername";
}
internal static class Socks
{
internal const string Enable = "SOCKSEnable";
internal const string Host = "SOCKSProxy";
internal const string Password = "SOCKSPassword";
internal const string Port = "SOCKSPort";
internal const string RequiresAuthentication = "SOCKSRequiresPassword";
internal const string Username = "SOCKSUsername";
}
}
}
internal static class ConfigurationFile

View file

@ -0,0 +1,56 @@
/*
* 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 System.Collections.Generic;
namespace SafeExamBrowser.Settings.Browser
{
/// <summary>
/// Defines the proxy settings for the browser engine.
/// </summary>
[Serializable]
public class BrowserProxySettings
{
/// <summary>
/// Determines whether proxy auto-configuration should be used. Requires a valid URL defined in <see cref="AutoConfigureUrl"/>.
/// </summary>
public bool AutoConfigure { get; set; }
/// <summary>
/// A valid URL to a proxy auto-configuration file (.pac). Is only evaluated if <see cref="AutoConfigure"/> is enabled.
/// </summary>
public string AutoConfigureUrl { get; set; }
/// <summary>
/// Forces proxy auto-detection by the browser engine.
/// </summary>
public bool AutoDetect { get; set; }
/// <summary>
/// A list of hosts for which all proxy settings should be bypassed.
/// </summary>
public IList<string> BypassList { get; set; }
/// <summary>
/// The proxy policy to be used.
/// </summary>
public ProxyPolicy Policy { get; set; }
/// <summary>
/// Defines all proxies to be used.
/// </summary>
public IList<ProxySettings> Proxies { get; set; }
public BrowserProxySettings()
{
BypassList = new List<string>();
Proxies = new List<ProxySettings>();
}
}
}

View file

@ -11,7 +11,7 @@ using System;
namespace SafeExamBrowser.Settings.Browser
{
/// <summary>
/// Defines all settings for the browser engine of the application.
/// Defines all settings for the browser engine.
/// </summary>
[Serializable]
public class BrowserSettings
@ -56,6 +56,11 @@ namespace SafeExamBrowser.Settings.Browser
/// </summary>
public PopupPolicy PopupPolicy { get; set; }
/// <summary>
/// Determines the proxy settings to be used by the browser.
/// </summary>
public BrowserProxySettings Proxy { get; set; }
/// <summary>
/// The URL with which the main browser window will be loaded.
/// </summary>
@ -71,6 +76,7 @@ namespace SafeExamBrowser.Settings.Browser
AdditionalWindow = new BrowserWindowSettings();
Filter = new BrowserFilterSettings();
MainWindow = new BrowserWindowSettings();
Proxy = new BrowserProxySettings();
}
}
}

View file

@ -0,0 +1,26 @@
/*
* 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/.
*/
namespace SafeExamBrowser.Settings.Browser
{
/// <summary>
/// Defines all currently supported proxy policies for the browser.
/// </summary>
public enum ProxyPolicy
{
/// <summary>
/// Use custom proxy settings as defined in <see cref="BrowserProxySettings"/>.
/// </summary>
Custom,
/// <summary>
/// Use the proxy settings of the operating system (i.e. ignore all custom settings defined in <see cref="BrowserProxySettings"/>).
/// </summary>
System
}
}

View file

@ -0,0 +1,21 @@
/*
* 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/.
*/
namespace SafeExamBrowser.Settings.Browser
{
/// <summary>
/// Defines all protocols currently supported for proxies.
/// </summary>
public enum ProxyProtocol
{
Ftp,
Http,
Https,
Socks
}
}

View file

@ -0,0 +1,49 @@
/*
* 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;
namespace SafeExamBrowser.Settings.Browser
{
/// <summary>
/// Defines the settings for a proxy server.
/// </summary>
[Serializable]
public class ProxySettings
{
/// <summary>
/// The host name or IP address of the proxy server.
/// </summary>
public string Host { get; set; }
/// <summary>
/// The password to be used for authentication.
/// </summary>
public string Password { get; set; }
/// <summary>
/// The port of the proxy server.
/// </summary>
public int Port { get; set; }
/// <summary>
/// The protocol of the proxy server.
/// </summary>
public ProxyProtocol Protocol { get; set; }
/// <summary>
/// Determines whether the proxy server requires authentication.
/// </summary>
public bool RequiresAuthentication { get; set; }
/// <summary>
/// The username to be used for authentication.
/// </summary>
public string Username { get; set; }
}
}

View file

@ -65,6 +65,10 @@
<Compile Include="Browser\FilterRuleSettings.cs" />
<Compile Include="Browser\FilterRuleType.cs" />
<Compile Include="Browser\PopupPolicy.cs" />
<Compile Include="Browser\ProxyPolicy.cs" />
<Compile Include="Browser\BrowserProxySettings.cs" />
<Compile Include="Browser\ProxyProtocol.cs" />
<Compile Include="Browser\ProxySettings.cs" />
<Compile Include="ConfigurationMode.cs" />
<Compile Include="KioskMode.cs" />
<Compile Include="Logging\LogLevel.cs" />