SEBWIN-219: Finished basic implementation of reconfiguration mechanism.

This commit is contained in:
dbuechel 2018-03-08 15:27:12 +01:00
parent 5bd1d9dea4
commit 993329bd71
27 changed files with 259 additions and 98 deletions

View file

@ -13,8 +13,10 @@ using System.Linq;
using CefSharp; using CefSharp;
using SafeExamBrowser.Browser.Handlers; using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.Contracts.Behaviour; using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.Taskbar; using SafeExamBrowser.Contracts.UserInterface.Taskbar;
using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.BrowserSettings; using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.BrowserSettings;
@ -26,6 +28,8 @@ namespace SafeExamBrowser.Browser
private IApplicationButton button; private IApplicationButton button;
private IList<IApplicationInstance> instances = new List<IApplicationInstance>(); private IList<IApplicationInstance> instances = new List<IApplicationInstance>();
private BrowserSettings settings; private BrowserSettings settings;
private ILogger logger;
private IRuntimeProxy runtime;
private RuntimeInfo runtimeInfo; private RuntimeInfo runtimeInfo;
private IUserInterfaceFactory uiFactory; private IUserInterfaceFactory uiFactory;
private IText text; private IText text;
@ -33,9 +37,13 @@ namespace SafeExamBrowser.Browser
public BrowserApplicationController( public BrowserApplicationController(
BrowserSettings settings, BrowserSettings settings,
RuntimeInfo runtimeInfo, RuntimeInfo runtimeInfo,
ILogger logger,
IRuntimeProxy runtime,
IText text, IText text,
IUserInterfaceFactory uiFactory) IUserInterfaceFactory uiFactory)
{ {
this.logger = logger;
this.runtime = runtime;
this.runtimeInfo = runtimeInfo; this.runtimeInfo = runtimeInfo;
this.settings = settings; this.settings = settings;
this.text = text; this.text = text;
@ -112,15 +120,32 @@ namespace SafeExamBrowser.Browser
private void Instance_ConfigurationDetected(string url, CancelEventArgs args) private void Instance_ConfigurationDetected(string url, CancelEventArgs args)
{ {
// TODO: var result = uiFactory.Show(TextKey.MessageBox_ReconfigurationQuestion, TextKey.MessageBox_ReconfigurationQuestionTitle, MessageBoxAction.YesNo, MessageBoxIcon.Question);
// 1. Ask whether reconfiguration should be attempted var reconfigure = result == MessageBoxResult.Yes;
// 2. Contact runtime and ask whether configuration valid and reconfiguration allowed var allowed = false;
// - If yes, do nothing and wait for shutdown command
// - If no, show message box and NAVIGATE TO PREVIOUS PAGE -> but how?
var result = uiFactory.Show(TextKey.MessageBox_ReconfigureQuestion, TextKey.MessageBox_ReconfigureQuestionTitle, MessageBoxAction.YesNo, MessageBoxIcon.Question); logger.Info($"Detected configuration request for '{url}'. The user chose to {(reconfigure ? "start" : "abort")} the reconfiguration.");
args.Cancel = result == MessageBoxResult.No; if (reconfigure)
{
try
{
allowed = runtime.RequestReconfiguration(url);
logger.Info($"The runtime {(allowed ? "accepted" : "denied")} the reconfiguration request.");
if (!allowed)
{
uiFactory.Show(TextKey.MessageBox_ReconfigurationDenied, TextKey.MessageBox_ReconfigurationDeniedTitle);
}
}
catch (Exception e)
{
logger.Error("Failed to communicate the reconfiguration request to the runtime!", e);
uiFactory.Show(TextKey.MessageBox_ReconfigurationError, TextKey.MessageBox_ReconfigurationErrorTitle, icon: MessageBoxIcon.Error);
}
}
args.Cancel = !allowed;
} }
private void Instance_Terminated(Guid id) private void Instance_Terminated(Guid id)

View file

@ -17,7 +17,7 @@ namespace SafeExamBrowser.Browser.Handlers
{ {
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request) public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request)
{ {
var page = "<html><body style=\"height: 90%; display: flex; align-items: center; justify-content: center\"><progress /></body></html>"; var page = @"<html><body style=""height: 90%; display: flex; align-items: center; justify-content: center""><progress /></body></html>";
var handler = ResourceHandler.FromString(page); var handler = ResourceHandler.FromString(page);
return handler; return handler;

View file

@ -48,6 +48,7 @@ namespace SafeExamBrowser.Client
private IClientHost clientHost; private IClientHost clientHost;
private ILogger logger; private ILogger logger;
private INativeMethods nativeMethods; private INativeMethods nativeMethods;
private IRuntimeProxy runtimeProxy;
private ISystemInfo systemInfo; private ISystemInfo systemInfo;
private IText text; private IText text;
private IUserInterfaceFactory uiFactory; private IUserInterfaceFactory uiFactory;
@ -68,8 +69,8 @@ namespace SafeExamBrowser.Client
text = new Text(logger); text = new Text(logger);
uiFactory = new UserInterfaceFactory(text); uiFactory = new UserInterfaceFactory(text);
runtimeProxy = new RuntimeProxy(runtimeHostUri, new ModuleLogger(logger, typeof(RuntimeProxy)));
var runtimeProxy = new RuntimeProxy(runtimeHostUri, new ModuleLogger(logger, typeof(RuntimeProxy)));
var displayMonitor = new DisplayMonitor(new ModuleLogger(logger, typeof(DisplayMonitor)), nativeMethods); var displayMonitor = new DisplayMonitor(new ModuleLogger(logger, typeof(DisplayMonitor)), nativeMethods);
var processMonitor = new ProcessMonitor(new ModuleLogger(logger, typeof(ProcessMonitor)), nativeMethods); var processMonitor = new ProcessMonitor(new ModuleLogger(logger, typeof(ProcessMonitor)), nativeMethods);
var windowMonitor = new WindowMonitor(new ModuleLogger(logger, typeof(WindowMonitor)), nativeMethods); var windowMonitor = new WindowMonitor(new ModuleLogger(logger, typeof(WindowMonitor)), nativeMethods);
@ -143,7 +144,8 @@ namespace SafeExamBrowser.Client
private IOperation BuildBrowserOperation() private IOperation BuildBrowserOperation()
{ {
var browserController = new BrowserApplicationController(configuration.Settings.Browser, configuration.RuntimeInfo, text, uiFactory); var moduleLogger = new ModuleLogger(logger, typeof(BrowserApplicationController));
var browserController = new BrowserApplicationController(configuration.Settings.Browser, configuration.RuntimeInfo, moduleLogger, runtimeProxy, text, uiFactory);
var browserInfo = new BrowserApplicationInfo(); var browserInfo = new BrowserApplicationInfo();
var operation = new BrowserOperation(browserController, browserInfo, logger, Taskbar, uiFactory); var operation = new BrowserOperation(browserController, browserInfo, logger, Taskbar, uiFactory);

View file

@ -18,8 +18,9 @@ namespace SafeExamBrowser.Configuration
{ {
private RuntimeInfo runtimeInfo; private RuntimeInfo runtimeInfo;
public ISession CurrentSession { get; private set; } public ISessionData CurrentSession { get; private set; }
public Settings CurrentSettings { get; private set; } public Settings CurrentSettings { get; private set; }
public string ReconfigurationUrl { get; set; }
public RuntimeInfo RuntimeInfo public RuntimeInfo RuntimeInfo
{ {
@ -34,9 +35,9 @@ namespace SafeExamBrowser.Configuration
} }
} }
public ISession InitializeSession() public ISessionData InitializeSessionData()
{ {
var session = new Session var session = new SessionData
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
StartupToken = Guid.NewGuid() StartupToken = Guid.NewGuid()

View file

@ -53,7 +53,7 @@
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Session.cs" /> <Compile Include="SessionData.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ConfigurationRepository.cs" /> <Compile Include="ConfigurationRepository.cs" />
<Compile Include="SystemInfo.cs" /> <Compile Include="SystemInfo.cs" />

View file

@ -12,7 +12,7 @@ using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Configuration namespace SafeExamBrowser.Configuration
{ {
public class Session : ISession public class SessionData : ISessionData
{ {
public IProcess ClientProcess { get; set; } public IProcess ClientProcess { get; set; }
public Guid Id { get; set; } public Guid Id { get; set; }

View file

@ -18,10 +18,12 @@ namespace SafeExamBrowser.Contracts.Communication
/// Defines the API for all communication between the three application components (runtime, service and client). /// Defines the API for all communication between the three application components (runtime, service and client).
/// </summary> /// </summary>
[ServiceContract(SessionMode = SessionMode.Required)] [ServiceContract(SessionMode = SessionMode.Required)]
[ServiceKnownType(typeof(SimpleMessage))]
[ServiceKnownType(typeof(AuthenticationResponse))] [ServiceKnownType(typeof(AuthenticationResponse))]
[ServiceKnownType(typeof(ConfigurationResponse))]
[ServiceKnownType(typeof(ClientConfiguration))] [ServiceKnownType(typeof(ClientConfiguration))]
[ServiceKnownType(typeof(ConfigurationResponse))]
[ServiceKnownType(typeof(ReconfigurationMessage))]
[ServiceKnownType(typeof(ReconfigurationResponse))]
[ServiceKnownType(typeof(SimpleMessage))]
[ServiceKnownType(typeof(SimpleResponse))] [ServiceKnownType(typeof(SimpleResponse))]
public interface ICommunication public interface ICommunication
{ {

View file

@ -30,6 +30,11 @@ namespace SafeExamBrowser.Contracts.Communication
/// </summary> /// </summary>
event CommunicationEventHandler ClientReady; event CommunicationEventHandler ClientReady;
/// <summary>
/// Event fired when the client detected a reconfiguration request.
/// </summary>
event CommunicationEventHandler ReconfigurationRequested;
/// <summary> /// <summary>
/// Event fired when the client requests to shut down the application. /// Event fired when the client requests to shut down the application.
/// </summary> /// </summary>

View file

@ -32,5 +32,12 @@ namespace SafeExamBrowser.Contracts.Communication
/// </summary> /// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception> /// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
void RequestShutdown(); void RequestShutdown();
/// <summary>
/// Requests the runtime to reconfigure the application with the configuration from the given location. Returns <c>true</c> if
/// the runtime accepted the request, otherwise <c>false</c>.
/// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
bool RequestReconfiguration(string url);
} }
} }

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2018 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.Contracts.Communication.Messages
{
/// <summary>
/// This message is transmitted to the runtime to request that the application be reconfigured.
/// </summary>
[Serializable]
public class ReconfigurationMessage : Message
{
/// <summary>
/// The locator of the new configuration to be used.
/// </summary>
public string ConfigurationUrl { get; private set; }
public ReconfigurationMessage(string url)
{
ConfigurationUrl = url;
}
}
}

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2018 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.Contracts.Communication.Responses
{
/// <summary>
/// The response to a <see cref="Messages.ReconfigurationMessage"/>.
/// </summary>
[Serializable]
public class ReconfigurationResponse : Response
{
/// <summary>
/// Indicates whether the reconfiguration request has been accepted.
/// </summary>
public bool Accepted { get; set; }
}
}

View file

@ -16,10 +16,10 @@ namespace SafeExamBrowser.Contracts.Configuration
public interface IConfigurationRepository public interface IConfigurationRepository
{ {
/// <summary> /// <summary>
/// Retrieves the current session, i.e. the last one which was initialized. If no session has been initialized yet, this /// Retrieves the current session data, i.e. the last one which was initialized. If no session has been initialized yet, this
/// property will be <c>null</c>! /// property will be <c>null</c>!
/// </summary> /// </summary>
ISession CurrentSession { get; } ISessionData CurrentSession { get; }
/// <summary> /// <summary>
/// Retrieves the current settings, i.e. the last ones which were loaded. If no settings have been loaded yet, this property will /// Retrieves the current settings, i.e. the last ones which were loaded. If no settings have been loaded yet, this property will
@ -27,6 +27,11 @@ namespace SafeExamBrowser.Contracts.Configuration
/// </summary> /// </summary>
Settings.Settings CurrentSettings { get; } Settings.Settings CurrentSettings { get; }
/// <summary>
/// The locator of the configuration to be used when reconfiguring the application.
/// </summary>
string ReconfigurationUrl { get; set; }
/// <summary> /// <summary>
/// The runtime information for the currently running application instance. /// The runtime information for the currently running application instance.
/// </summary> /// </summary>
@ -40,7 +45,7 @@ namespace SafeExamBrowser.Contracts.Configuration
/// <summary> /// <summary>
/// Initializes all relevant data for a new session. /// Initializes all relevant data for a new session.
/// </summary> /// </summary>
ISession InitializeSession(); ISessionData InitializeSessionData();
/// <summary> /// <summary>
/// Attempts to load settings from the specified path. /// Attempts to load settings from the specified path.

View file

@ -14,7 +14,7 @@ namespace SafeExamBrowser.Contracts.Configuration
/// <summary> /// <summary>
/// Defines all session-related (configuration) data. /// Defines all session-related (configuration) data.
/// </summary> /// </summary>
public interface ISession public interface ISessionData
{ {
/// <summary> /// <summary>
/// The process information of the client instance associated to this session. /// The process information of the client instance associated to this session.

View file

@ -18,14 +18,18 @@ namespace SafeExamBrowser.Contracts.I18n
LogWindow_Title, LogWindow_Title,
MessageBox_ApplicationError, MessageBox_ApplicationError,
MessageBox_ApplicationErrorTitle, MessageBox_ApplicationErrorTitle,
MessageBox_ConfigureClientSuccess, MessageBox_ClientConfigurationQuestion,
MessageBox_ConfigureClientSuccessTitle, MessageBox_ClientConfigurationQuestionTitle,
MessageBox_Quit, MessageBox_Quit,
MessageBox_QuitTitle, MessageBox_QuitTitle,
MessageBox_QuitError, MessageBox_QuitError,
MessageBox_QuitErrorTitle, MessageBox_QuitErrorTitle,
MessageBox_ReconfigureQuestion, MessageBox_ReconfigurationDenied,
MessageBox_ReconfigureQuestionTitle, MessageBox_ReconfigurationDeniedTitle,
MessageBox_ReconfigurationError,
MessageBox_ReconfigurationErrorTitle,
MessageBox_ReconfigurationQuestion,
MessageBox_ReconfigurationQuestionTitle,
MessageBox_SessionStartError, MessageBox_SessionStartError,
MessageBox_SessionStartErrorTitle, MessageBox_SessionStartErrorTitle,
MessageBox_SessionStopError, MessageBox_SessionStopError,

View file

@ -67,18 +67,20 @@
<Compile Include="Communication\IServiceProxy.cs" /> <Compile Include="Communication\IServiceProxy.cs" />
<Compile Include="Communication\Messages\Message.cs" /> <Compile Include="Communication\Messages\Message.cs" />
<Compile Include="Communication\Messages\DisconnectionMessage.cs" /> <Compile Include="Communication\Messages\DisconnectionMessage.cs" />
<Compile Include="Communication\Messages\ReconfigurationMessage.cs" />
<Compile Include="Communication\Messages\SimpleMessagePurport.cs" /> <Compile Include="Communication\Messages\SimpleMessagePurport.cs" />
<Compile Include="Communication\Messages\SimpleMessage.cs" /> <Compile Include="Communication\Messages\SimpleMessage.cs" />
<Compile Include="Communication\Responses\AuthenticationResponse.cs" /> <Compile Include="Communication\Responses\AuthenticationResponse.cs" />
<Compile Include="Communication\Responses\ConfigurationResponse.cs" /> <Compile Include="Communication\Responses\ConfigurationResponse.cs" />
<Compile Include="Communication\Responses\ConnectionResponse.cs" /> <Compile Include="Communication\Responses\ConnectionResponse.cs" />
<Compile Include="Communication\Responses\DisconnectionResponse.cs" /> <Compile Include="Communication\Responses\DisconnectionResponse.cs" />
<Compile Include="Communication\Responses\ReconfigurationResponse.cs" />
<Compile Include="Communication\Responses\Response.cs" /> <Compile Include="Communication\Responses\Response.cs" />
<Compile Include="Communication\Responses\SimpleResponsePurport.cs" /> <Compile Include="Communication\Responses\SimpleResponsePurport.cs" />
<Compile Include="Communication\Responses\SimpleResponse.cs" /> <Compile Include="Communication\Responses\SimpleResponse.cs" />
<Compile Include="Configuration\ClientConfiguration.cs" /> <Compile Include="Configuration\ClientConfiguration.cs" />
<Compile Include="Configuration\RuntimeInfo.cs" /> <Compile Include="Configuration\RuntimeInfo.cs" />
<Compile Include="Configuration\ISession.cs" /> <Compile Include="Configuration\ISessionData.cs" />
<Compile Include="Configuration\Settings\ConfigurationMode.cs" /> <Compile Include="Configuration\Settings\ConfigurationMode.cs" />
<Compile Include="Behaviour\INotificationController.cs" /> <Compile Include="Behaviour\INotificationController.cs" />
<Compile Include="Behaviour\OperationModel\IOperation.cs" /> <Compile Include="Behaviour\OperationModel\IOperation.cs" />

View file

@ -26,7 +26,6 @@ namespace SafeExamBrowser.Core.Communication
private readonly object @lock = new object(); private readonly object @lock = new object();
private string address; private string address;
private ILogger logger;
private ServiceHost host; private ServiceHost host;
private Thread hostThread; private Thread hostThread;
@ -47,7 +46,7 @@ namespace SafeExamBrowser.Core.Communication
public BaseHost(string address, ILogger logger) public BaseHost(string address, ILogger logger)
{ {
this.address = address; this.address = address;
this.logger = logger; this.Logger = logger;
} }
protected abstract bool OnConnect(Guid? token); protected abstract bool OnConnect(Guid? token);
@ -59,7 +58,7 @@ namespace SafeExamBrowser.Core.Communication
{ {
lock (@lock) lock (@lock)
{ {
logger.Debug($"Received connection request with authentication token '{token}'."); Logger.Debug($"Received connection request with authentication token '{token}'.");
var response = new ConnectionResponse(); var response = new ConnectionResponse();
var connected = OnConnect(token); var connected = OnConnect(token);
@ -70,7 +69,7 @@ namespace SafeExamBrowser.Core.Communication
response.ConnectionEstablished = true; response.ConnectionEstablished = true;
} }
logger.Debug($"{(connected ? "Accepted" : "Denied")} connection request."); Logger.Debug($"{(connected ? "Accepted" : "Denied")} connection request.");
return response; return response;
} }
@ -82,7 +81,7 @@ namespace SafeExamBrowser.Core.Communication
{ {
var response = new DisconnectionResponse(); var response = new DisconnectionResponse();
logger.Debug($"Received disconnection request with message '{ToString(message)}'."); Logger.Debug($"Received disconnection request with message '{ToString(message)}'.");
if (IsAuthorized(message?.CommunicationToken)) if (IsAuthorized(message?.CommunicationToken))
{ {
@ -118,7 +117,7 @@ namespace SafeExamBrowser.Core.Communication
} }
} }
logger.Debug($"Received message '{ToString(message)}', sending response '{ToString(response)}'."); Logger.Debug($"Received message '{ToString(message)}', sending response '{ToString(response)}'.");
return response; return response;
} }
@ -153,7 +152,7 @@ namespace SafeExamBrowser.Core.Communication
if (success) if (success)
{ {
logger.Debug($"Terminated communication host for endpoint '{address}'."); Logger.Debug($"Terminated communication host for endpoint '{address}'.");
} }
else else
{ {
@ -183,7 +182,7 @@ namespace SafeExamBrowser.Core.Communication
host.UnknownMessageReceived += Host_UnknownMessageReceived; host.UnknownMessageReceived += Host_UnknownMessageReceived;
host.Open(); host.Open();
logger.Debug($"Successfully started communication host for endpoint '{address}'."); Logger.Debug($"Successfully started communication host for endpoint '{address}'.");
startedEvent.Set(); startedEvent.Set();
} }
@ -215,32 +214,32 @@ namespace SafeExamBrowser.Core.Communication
private void Host_Closed(object sender, EventArgs e) private void Host_Closed(object sender, EventArgs e)
{ {
logger.Debug("Communication host has been closed."); Logger.Debug("Communication host has been closed.");
} }
private void Host_Closing(object sender, EventArgs e) private void Host_Closing(object sender, EventArgs e)
{ {
logger.Debug("Communication host is closing..."); Logger.Debug("Communication host is closing...");
} }
private void Host_Faulted(object sender, EventArgs e) private void Host_Faulted(object sender, EventArgs e)
{ {
logger.Error("Communication host has faulted!"); Logger.Error("Communication host has faulted!");
} }
private void Host_Opened(object sender, EventArgs e) private void Host_Opened(object sender, EventArgs e)
{ {
logger.Debug("Communication host has been opened."); Logger.Debug("Communication host has been opened.");
} }
private void Host_Opening(object sender, EventArgs e) private void Host_Opening(object sender, EventArgs e)
{ {
logger.Debug("Communication host is opening..."); Logger.Debug("Communication host is opening...");
} }
private void Host_UnknownMessageReceived(object sender, UnknownMessageReceivedEventArgs e) private void Host_UnknownMessageReceived(object sender, UnknownMessageReceivedEventArgs e)
{ {
logger.Warn($"Communication host has received an unknown message: {e?.Message}."); Logger.Warn($"Communication host has received an unknown message: {e?.Message}.");
} }
private string ToString(Message message) private string ToString(Message message)

View file

@ -51,7 +51,7 @@ namespace SafeExamBrowser.Core.Communication
(channel as ICommunicationObject).Opened += BaseProxy_Opened; (channel as ICommunicationObject).Opened += BaseProxy_Opened;
(channel as ICommunicationObject).Opening += BaseProxy_Opening; (channel as ICommunicationObject).Opening += BaseProxy_Opening;
Logger.Debug($"Trying to connect to endpoint {address}{(token.HasValue ? $" with authentication token '{token}'" : string.Empty)}..."); Logger.Debug($"Trying to connect to endpoint '{address}'{(token.HasValue ? $" with authentication token '{token}'" : string.Empty)}...");
var response = channel.Connect(token); var response = channel.Connect(token);
@ -125,7 +125,6 @@ namespace SafeExamBrowser.Core.Communication
private void BaseProxy_Faulted(object sender, EventArgs e) private void BaseProxy_Faulted(object sender, EventArgs e)
{ {
Logger.Error("Communication channel has faulted!"); Logger.Error("Communication channel has faulted!");
ConnectionLost?.Invoke();
} }
private void BaseProxy_Opened(object sender, EventArgs e) private void BaseProxy_Opened(object sender, EventArgs e)

View file

@ -46,6 +46,18 @@ namespace SafeExamBrowser.Core.Communication
} }
} }
public bool RequestReconfiguration(string url)
{
var response = Send(new ReconfigurationMessage(url));
if (response is ReconfigurationResponse reconfiguration)
{
return reconfiguration.Accepted;
}
return false;
}
public void RequestShutdown() public void RequestShutdown()
{ {
var response = Send(SimpleMessagePurport.RequestShutdown); var response = Send(SimpleMessagePurport.RequestShutdown);

View file

@ -12,10 +12,10 @@
<Entry key="MessageBox_ApplicationErrorTitle"> <Entry key="MessageBox_ApplicationErrorTitle">
Application Error Application Error
</Entry> </Entry>
<Entry key="MessageBox_ConfigureClientSuccess"> <Entry key="MessageBox_ClientConfigurationQuestion">
The client configuration has been saved and will be used when you start the application the next time. Do you want to quit for now? The client configuration has been saved and will be used when you start the application the next time. Do you want to quit for now?
</Entry> </Entry>
<Entry key="MessageBox_ConfigureClientSuccessTitle"> <Entry key="MessageBox_ClientConfigurationQuestionTitle">
Configuration Successful Configuration Successful
</Entry> </Entry>
<Entry key="MessageBox_Quit"> <Entry key="MessageBox_Quit">
@ -25,15 +25,27 @@
Quit? Quit?
</Entry> </Entry>
<Entry key="MessageBox_QuitError"> <Entry key="MessageBox_QuitError">
The client failed to communicate the shutdown request to the runtime! Please try again... The client failed to communicate the shutdown request to the runtime!
</Entry> </Entry>
<Entry key="MessageBox_QuitErrorTitle"> <Entry key="MessageBox_QuitErrorTitle">
Quit Error Quit Error
</Entry> </Entry>
<Entry key="MessageBox_ReconfigureQuestion"> <Entry key="MessageBox_ReconfigurationDenied">
You are not allowed to reconfigure the application.
</Entry>
<Entry key="MessageBox_ReconfigurationDeniedTitle">
Reconfiguration Denied
</Entry>
<Entry key="MessageBox_ReconfigurationError">
The client failed to communicate the reconfiguration request to the runtime!
</Entry>
<Entry key="MessageBox_ReconfigurationErrorTitle">
Reconfiguration Error
</Entry>
<Entry key="MessageBox_ReconfigurationQuestion">
Would you like to reconfigure the application? Would you like to reconfigure the application?
</Entry> </Entry>
<Entry key="MessageBox_ReconfigureQuestionTitle"> <Entry key="MessageBox_ReconfigurationQuestionTitle">
Configuration Detected Configuration Detected
</Entry> </Entry>
<Entry key="MessageBox_SessionStartError"> <Entry key="MessageBox_SessionStartError">

View file

@ -127,8 +127,8 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
private bool IsConfigurationSufficient() private bool IsConfigurationSufficient()
{ {
var message = text.Get(TextKey.MessageBox_ConfigureClientSuccess); var message = text.Get(TextKey.MessageBox_ClientConfigurationQuestion);
var title = text.Get(TextKey.MessageBox_ConfigureClientSuccessTitle); var title = text.Get(TextKey.MessageBox_ClientConfigurationQuestionTitle);
var abort = uiFactory.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question); var abort = uiFactory.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question);
return abort == MessageBoxResult.Yes; return abort == MessageBoxResult.Yes;

View file

@ -7,38 +7,39 @@
*/ */
using SafeExamBrowser.Contracts.Behaviour.OperationModel; using SafeExamBrowser.Contracts.Behaviour.OperationModel;
using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Runtime.Behaviour.Operations namespace SafeExamBrowser.Runtime.Behaviour.Operations
{ {
internal class SessionSequenceEndOperation : SessionSequenceOperation internal class SessionSequenceEndOperation : IOperation
{ {
public SessionSequenceEndOperation( private SessionController controller;
IClientProxy client,
IConfigurationRepository configuration, public IProgressIndicator ProgressIndicator { private get; set; }
ILogger logger,
IProcessFactory processFactory, public SessionSequenceEndOperation(SessionController controller)
IRuntimeHost runtimeHost,
IServiceProxy service) : base(client, configuration, logger, processFactory, runtimeHost, service)
{ {
this.controller = controller;
} }
public override OperationResult Perform() public OperationResult Perform()
{ {
return StartSession(); controller.ProgressIndicator = ProgressIndicator;
return controller.StartSession();
} }
public override OperationResult Repeat() public OperationResult Repeat()
{ {
return StartSession(); controller.ProgressIndicator = ProgressIndicator;
return controller.StartSession();
} }
public override void Revert() public void Revert()
{ {
StopSession(); controller.ProgressIndicator = ProgressIndicator;
controller.StopSession();
} }
} }
} }

View file

@ -7,36 +7,34 @@
*/ */
using SafeExamBrowser.Contracts.Behaviour.OperationModel; using SafeExamBrowser.Contracts.Behaviour.OperationModel;
using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Runtime.Behaviour.Operations namespace SafeExamBrowser.Runtime.Behaviour.Operations
{ {
internal class SessionSequenceStartOperation : SessionSequenceOperation internal class SessionSequenceStartOperation : IOperation
{ {
public SessionSequenceStartOperation( private SessionController controller;
IClientProxy client,
IConfigurationRepository configuration, public IProgressIndicator ProgressIndicator { private get; set; }
ILogger logger,
IProcessFactory processFactory, public SessionSequenceStartOperation(SessionController controller)
IRuntimeHost runtimeHost,
IServiceProxy service) : base(client, configuration, logger, processFactory, runtimeHost, service)
{ {
this.controller = controller;
} }
public override OperationResult Perform() public OperationResult Perform()
{ {
return OperationResult.Success; return OperationResult.Success;
} }
public override OperationResult Repeat() public OperationResult Repeat()
{ {
return StopSession(); controller.ProgressIndicator = ProgressIndicator;
return controller.StopSession();
} }
public override void Revert() public void Revert()
{ {
// Nothing to do here... // Nothing to do here...
} }

View file

@ -134,6 +134,7 @@ namespace SafeExamBrowser.Runtime.Behaviour
{ {
runtimeWindow.Show(); runtimeWindow.Show();
runtimeWindow.BringToForeground(); runtimeWindow.BringToForeground();
runtimeWindow.ShowProgressBar();
logger.Info(">>>--- Initiating session procedure ---<<<"); logger.Info(">>>--- Initiating session procedure ---<<<");
if (sessionRunning) if (sessionRunning)
@ -170,6 +171,7 @@ namespace SafeExamBrowser.Runtime.Behaviour
if (!initial) if (!initial)
{ {
logger.Info("Terminating application...");
shutdown.Invoke(); shutdown.Invoke();
} }
} }
@ -201,6 +203,7 @@ namespace SafeExamBrowser.Runtime.Behaviour
private void RegisterEvents() private void RegisterEvents()
{ {
client.ConnectionLost += Client_ConnectionLost; client.ConnectionLost += Client_ConnectionLost;
runtimeHost.ReconfigurationRequested += RuntimeHost_ReconfigurationRequested;
runtimeHost.ShutdownRequested += RuntimeHost_ShutdownRequested; runtimeHost.ShutdownRequested += RuntimeHost_ShutdownRequested;
} }
@ -212,6 +215,7 @@ namespace SafeExamBrowser.Runtime.Behaviour
private void DeregisterEvents() private void DeregisterEvents()
{ {
client.ConnectionLost -= Client_ConnectionLost; client.ConnectionLost -= Client_ConnectionLost;
runtimeHost.ReconfigurationRequested -= RuntimeHost_ReconfigurationRequested;
runtimeHost.ShutdownRequested -= RuntimeHost_ShutdownRequested; runtimeHost.ShutdownRequested -= RuntimeHost_ShutdownRequested;
} }
@ -238,6 +242,12 @@ namespace SafeExamBrowser.Runtime.Behaviour
shutdown.Invoke(); shutdown.Invoke();
} }
private void RuntimeHost_ReconfigurationRequested()
{
logger.Info($"Starting reconfiguration...");
StartSession();
}
private void RuntimeHost_ShutdownRequested() private void RuntimeHost_ShutdownRequested()
{ {
logger.Info("Received shutdown request from the client application."); logger.Info("Received shutdown request from the client application.");

View file

@ -15,9 +15,9 @@ using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.WindowsApi; using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Runtime.Behaviour.Operations namespace SafeExamBrowser.Runtime.Behaviour
{ {
internal abstract class SessionSequenceOperation : IOperation internal class SessionController
{ {
private const int TEN_SECONDS = 10000; private const int TEN_SECONDS = 10000;
@ -28,11 +28,11 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
private IProcessFactory processFactory; private IProcessFactory processFactory;
private IRuntimeHost runtimeHost; private IRuntimeHost runtimeHost;
private IServiceProxy service; private IServiceProxy service;
private ISession session; private ISessionData session;
public IProgressIndicator ProgressIndicator { private get; set; } internal IProgressIndicator ProgressIndicator { private get; set; }
public SessionSequenceOperation( internal SessionController(
IClientProxy client, IClientProxy client,
IConfigurationRepository configuration, IConfigurationRepository configuration,
ILogger logger, ILogger logger,
@ -48,16 +48,12 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
this.service = service; this.service = service;
} }
public abstract OperationResult Perform(); internal OperationResult StartSession()
public abstract OperationResult Repeat();
public abstract void Revert();
protected OperationResult StartSession()
{ {
logger.Info("Starting new session..."); logger.Info("Starting new session...");
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StartSession, true); ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StartSession, true);
session = configuration.InitializeSession(); session = configuration.InitializeSessionData();
runtimeHost.StartupToken = session.StartupToken; runtimeHost.StartupToken = session.StartupToken;
logger.Info("Initializing service session..."); logger.Info("Initializing service session...");
@ -78,7 +74,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
return OperationResult.Success; return OperationResult.Success;
} }
protected OperationResult StopSession() internal OperationResult StopSession()
{ {
if (sessionRunning) if (sessionRunning)
{ {

View file

@ -7,10 +7,12 @@
*/ */
using System; using System;
using System.Threading.Tasks;
using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Messages; using SafeExamBrowser.Contracts.Communication.Messages;
using SafeExamBrowser.Contracts.Communication.Responses; using SafeExamBrowser.Contracts.Communication.Responses;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Core.Communication; using SafeExamBrowser.Core.Communication;
@ -25,6 +27,7 @@ namespace SafeExamBrowser.Runtime.Communication
public event CommunicationEventHandler ClientDisconnected; public event CommunicationEventHandler ClientDisconnected;
public event CommunicationEventHandler ClientReady; public event CommunicationEventHandler ClientReady;
public event CommunicationEventHandler ReconfigurationRequested;
public event CommunicationEventHandler ShutdownRequested; public event CommunicationEventHandler ShutdownRequested;
public RuntimeHost(string address, IConfigurationRepository configuration, ILogger logger) : base(address, logger) public RuntimeHost(string address, IConfigurationRepository configuration, ILogger logger) : base(address, logger)
@ -48,10 +51,17 @@ namespace SafeExamBrowser.Runtime.Communication
protected override void OnDisconnect() protected override void OnDisconnect()
{ {
ClientDisconnected?.Invoke(); ClientDisconnected?.Invoke();
allowConnection = true;
} }
protected override Response OnReceive(Message message) protected override Response OnReceive(Message message)
{ {
switch (message)
{
case ReconfigurationMessage reconfigurationMessage:
return Handle(reconfigurationMessage);
}
return new SimpleResponse(SimpleResponsePurport.UnknownMessage); return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
} }
@ -71,5 +81,22 @@ namespace SafeExamBrowser.Runtime.Communication
return new SimpleResponse(SimpleResponsePurport.UnknownMessage); return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
} }
private Response Handle(ReconfigurationMessage message)
{
var isExam = configuration.CurrentSettings.ConfigurationMode == ConfigurationMode.Exam;
var isValidUri = Uri.TryCreate(message.ConfigurationUrl, UriKind.Absolute, out _);
var allowed = !isExam && isValidUri;
Logger.Info($"Received reconfiguration request for '{message.ConfigurationUrl}', {(allowed ? "accepted" : "denied")} it.");
if (allowed)
{
configuration.ReconfigurationUrl = message.ConfigurationUrl;
Task.Run(() => ReconfigurationRequested?.Invoke());
}
return new ReconfigurationResponse { Accepted = allowed };
}
} }
} }

View file

@ -52,6 +52,7 @@ namespace SafeExamBrowser.Runtime
var clientProxy = new ClientProxy(runtimeInfo.ClientAddress, new ModuleLogger(logger, typeof(ClientProxy))); var clientProxy = new ClientProxy(runtimeInfo.ClientAddress, new ModuleLogger(logger, typeof(ClientProxy)));
var runtimeHost = new RuntimeHost(runtimeInfo.RuntimeAddress, configuration, new ModuleLogger(logger, typeof(RuntimeHost))); var runtimeHost = new RuntimeHost(runtimeInfo.RuntimeAddress, configuration, new ModuleLogger(logger, typeof(RuntimeHost)));
var serviceProxy = new ServiceProxy(runtimeInfo.ServiceAddress, new ModuleLogger(logger, typeof(ServiceProxy))); var serviceProxy = new ServiceProxy(runtimeInfo.ServiceAddress, new ModuleLogger(logger, typeof(ServiceProxy)));
var sessionController = new SessionController(clientProxy, configuration, logger, processFactory, runtimeHost, serviceProxy);
var bootstrapOperations = new Queue<IOperation>(); var bootstrapOperations = new Queue<IOperation>();
var sessionOperations = new Queue<IOperation>(); var sessionOperations = new Queue<IOperation>();
@ -59,11 +60,11 @@ namespace SafeExamBrowser.Runtime
bootstrapOperations.Enqueue(new I18nOperation(logger, text)); bootstrapOperations.Enqueue(new I18nOperation(logger, text));
bootstrapOperations.Enqueue(new CommunicationOperation(runtimeHost, logger)); bootstrapOperations.Enqueue(new CommunicationOperation(runtimeHost, logger));
sessionOperations.Enqueue(new SessionSequenceStartOperation(clientProxy, configuration, logger, processFactory, runtimeHost, serviceProxy)); sessionOperations.Enqueue(new SessionSequenceStartOperation(sessionController));
sessionOperations.Enqueue(new ConfigurationOperation(configuration, logger, runtimeInfo, text, uiFactory, args)); sessionOperations.Enqueue(new ConfigurationOperation(configuration, logger, runtimeInfo, text, uiFactory, args));
sessionOperations.Enqueue(new ServiceConnectionOperation(configuration, logger, serviceProxy, text)); sessionOperations.Enqueue(new ServiceConnectionOperation(configuration, logger, serviceProxy, text));
sessionOperations.Enqueue(new KioskModeOperation(logger, configuration)); sessionOperations.Enqueue(new KioskModeOperation(logger, configuration));
sessionOperations.Enqueue(new SessionSequenceEndOperation(clientProxy, configuration, logger, processFactory, runtimeHost, serviceProxy)); sessionOperations.Enqueue(new SessionSequenceEndOperation(sessionController));
var boostrapSequence = new OperationSequence(logger, bootstrapOperations); var boostrapSequence = new OperationSequence(logger, bootstrapOperations);
var sessionSequence = new OperationSequence(logger, sessionOperations); var sessionSequence = new OperationSequence(logger, sessionOperations);

View file

@ -91,7 +91,7 @@
<Compile Include="Behaviour\Operations\KioskModeOperation.cs" /> <Compile Include="Behaviour\Operations\KioskModeOperation.cs" />
<Compile Include="Behaviour\Operations\ServiceConnectionOperation.cs" /> <Compile Include="Behaviour\Operations\ServiceConnectionOperation.cs" />
<Compile Include="Behaviour\Operations\SessionSequenceEndOperation.cs" /> <Compile Include="Behaviour\Operations\SessionSequenceEndOperation.cs" />
<Compile Include="Behaviour\Operations\SessionSequenceOperation.cs" /> <Compile Include="Behaviour\SessionController.cs" />
<Compile Include="Behaviour\Operations\SessionSequenceStartOperation.cs" /> <Compile Include="Behaviour\Operations\SessionSequenceStartOperation.cs" />
<Compile Include="Communication\RuntimeHost.cs" /> <Compile Include="Communication\RuntimeHost.cs" />
<Compile Include="CompositionRoot.cs" /> <Compile Include="CompositionRoot.cs" />