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,16 +23,34 @@ 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 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)
@ -38,7 +58,7 @@ namespace SafeExamBrowser.Browser.Handlers
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" />