SEBWIN-405: Implemented scaffolding for SEB server operation.

This commit is contained in:
Damian Büchel 2020-07-13 22:57:19 +02:00
parent 92ab58f988
commit 0edca494b3
17 changed files with 573 additions and 66 deletions

View file

@ -119,6 +119,7 @@ namespace SafeExamBrowser.I18n.Contracts
OperationStatus_CloseRuntimeConnection,
OperationStatus_EmptyClipboard,
OperationStatus_FinalizeApplications,
OperationStatus_FinalizeServer,
OperationStatus_FinalizeServiceSession,
OperationStatus_FinalizeSystemEvents,
OperationStatus_InitializeApplications,
@ -126,6 +127,7 @@ namespace SafeExamBrowser.I18n.Contracts
OperationStatus_InitializeConfiguration,
OperationStatus_InitializeKioskMode,
OperationStatus_InitializeRuntimeConnection,
OperationStatus_InitializeServer,
OperationStatus_InitializeServiceSession,
OperationStatus_InitializeSession,
OperationStatus_InitializeShell,

View file

@ -315,6 +315,9 @@
<Entry key="OperationStatus_FinalizeApplications">
Beende Applikationen
</Entry>
<Entry key="OperationStatus_FinalizeServer">
Finalisiere SEB-Server
</Entry>
<Entry key="OperationStatus_FinalizeServiceSession">
Beende Service-Sitzung
</Entry>
@ -336,6 +339,9 @@
<Entry key="OperationStatus_InitializeRuntimeConnection">
Initialisiere Verbindung zur Runtime
</Entry>
<Entry key="OperationStatus_InitializeServer">
Initialisiere SEB-Server
</Entry>
<Entry key="OperationStatus_InitializeServiceSession">
Initialisiere Service-Sitzung
</Entry>

View file

@ -315,6 +315,9 @@
<Entry key="OperationStatus_FinalizeApplications">
Finalizing applications
</Entry>
<Entry key="OperationStatus_FinalizeServer">
Finalizing SEB-Server
</Entry>
<Entry key="OperationStatus_FinalizeServiceSession">
Finalizing service session
</Entry>
@ -336,6 +339,9 @@
<Entry key="OperationStatus_InitializeRuntimeConnection">
Initializing runtime connection
</Entry>
<Entry key="OperationStatus_InitializeServer">
Initializing SEB-Server
</Entry>
<Entry key="OperationStatus_InitializeServiceSession">
Initializing service session
</Entry>

View file

@ -26,6 +26,7 @@ using SafeExamBrowser.Logging;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Runtime.Communication;
using SafeExamBrowser.Runtime.Operations;
using SafeExamBrowser.Server;
using SafeExamBrowser.Settings.Logging;
using SafeExamBrowser.SystemComponents;
using SafeExamBrowser.SystemComponents.Contracts;
@ -66,6 +67,7 @@ namespace SafeExamBrowser.Runtime
var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), ModuleLogger(nameof(ProxyFactory)));
var runtimeHost = new RuntimeHost(appConfig.RuntimeAddress, new HostObjectFactory(), ModuleLogger(nameof(RuntimeHost)), FIVE_SECONDS);
var runtimeWindow = uiFactory.CreateRuntimeWindow(appConfig);
var server = new ServerProxy();
var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), ModuleLogger(nameof(ServiceProxy)), Interlocutor.Runtime);
var sessionContext = new SessionContext();
var splashScreen = uiFactory.CreateSplashScreen(appConfig);
@ -80,7 +82,7 @@ namespace SafeExamBrowser.Runtime
sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost, sessionContext));
sessionOperations.Enqueue(new ConfigurationOperation(args, configuration, new FileSystem(), new HashAlgorithm(), logger, sessionContext));
// TODO: sessionOperations.Enqueue(new ServerOperation());
sessionOperations.Enqueue(new ServerOperation(args, configuration, logger, sessionContext, server));
sessionOperations.Enqueue(new VirtualMachineOperation(vmDetector, logger, sessionContext));
sessionOperations.Enqueue(new ServiceOperation(logger, runtimeHost, serviceProxy, sessionContext, THIRTY_SECONDS, userInfo));
sessionOperations.Enqueue(new ClientTerminationOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, THIRTY_SECONDS));
@ -108,7 +110,7 @@ namespace SafeExamBrowser.Runtime
}
internal void LogStartupInformation()
{
{
logger.Log($"/* {appConfig.ProgramTitle}, Version {appConfig.ProgramInformationalVersion}, Build {appConfig.ProgramBuildVersion}");
logger.Log($"/* {appConfig.ProgramCopyright}");
logger.Log($"/* ");

View file

@ -0,0 +1,91 @@
/*
* Copyright (c) 2020 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.Communication.Contracts.Data;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.Runtime.Operations.Events;
using SafeExamBrowser.Settings;
namespace SafeExamBrowser.Runtime.Operations
{
internal abstract class ConfigurationBaseOperation : SessionOperation
{
protected string[] commandLineArgs;
protected IConfigurationRepository configuration;
protected string AppDataFilePath => Context.Next.AppConfig.AppDataFilePath;
protected string ProgramDataFilePath => Context.Next.AppConfig.ProgramDataFilePath;
public ConfigurationBaseOperation(string[] commandLineArgs, IConfigurationRepository configuration, SessionContext context) : base(context)
{
this.commandLineArgs = commandLineArgs;
this.configuration = configuration;
}
protected abstract void InvokeActionRequired(ActionRequiredEventArgs args);
protected LoadStatus? TryLoadSettings(Uri uri, UriSource source, out PasswordParameters passwordParams, out AppSettings settings, string currentPassword = default(string))
{
passwordParams = new PasswordParameters { Password = string.Empty, IsHash = true };
var status = configuration.TryLoadSettings(uri, out settings, passwordParams);
if (status == LoadStatus.PasswordNeeded && currentPassword != default(string))
{
passwordParams.Password = currentPassword;
passwordParams.IsHash = true;
status = configuration.TryLoadSettings(uri, out settings, passwordParams);
}
for (int attempts = 0; attempts < 5 && status == LoadStatus.PasswordNeeded; attempts++)
{
var isLocalConfig = source == UriSource.AppData || source == UriSource.ProgramData;
var purpose = isLocalConfig ? PasswordRequestPurpose.LocalSettings : PasswordRequestPurpose.Settings;
var success = TryGetPassword(purpose, out var password);
if (success)
{
passwordParams.Password = password;
passwordParams.IsHash = false;
}
else
{
return null;
}
status = configuration.TryLoadSettings(uri, out settings, passwordParams);
}
return status;
}
protected bool TryGetPassword(PasswordRequestPurpose purpose, out string password)
{
var args = new PasswordRequiredEventArgs { Purpose = purpose };
InvokeActionRequired(args);
password = args.Password;
return args.Success;
}
protected enum UriSource
{
Undefined,
AppData,
CommandLine,
ProgramData,
Reconfiguration,
Server
}
}
}

View file

@ -21,17 +21,12 @@ using SafeExamBrowser.SystemComponents.Contracts;
namespace SafeExamBrowser.Runtime.Operations
{
internal class ConfigurationOperation : SessionOperation
internal class ConfigurationOperation : ConfigurationBaseOperation
{
private string[] commandLineArgs;
private IConfigurationRepository configuration;
private IFileSystem fileSystem;
private IHashAlgorithm hashAlgorithm;
private ILogger logger;
private string AppDataFilePath => Context.Next.AppConfig.AppDataFilePath;
private string ProgramDataFilePath => Context.Next.AppConfig.ProgramDataFilePath;
public override event ActionRequiredEventHandler ActionRequired;
public override event StatusChangedEventHandler StatusChanged;
@ -41,10 +36,8 @@ namespace SafeExamBrowser.Runtime.Operations
IFileSystem fileSystem,
IHashAlgorithm hashAlgorithm,
ILogger logger,
SessionContext sessionContext) : base(sessionContext)
SessionContext sessionContext) : base(commandLineArgs, configuration, sessionContext)
{
this.commandLineArgs = commandLineArgs;
this.configuration = configuration;
this.fileSystem = fileSystem;
this.hashAlgorithm = hashAlgorithm;
this.logger = logger;
@ -99,6 +92,11 @@ namespace SafeExamBrowser.Runtime.Operations
return OperationResult.Success;
}
protected override void InvokeActionRequired(ActionRequiredEventArgs args)
{
ActionRequired?.Invoke(args);
}
private OperationResult LoadDefaultSettings()
{
logger.Info("No valid configuration resource specified and no local client configuration found - loading default settings...");
@ -232,42 +230,6 @@ namespace SafeExamBrowser.Runtime.Operations
return result;
}
private LoadStatus? TryLoadSettings(Uri uri, UriSource source, out PasswordParameters passwordParams, out AppSettings settings, string currentPassword = default(string))
{
passwordParams = new PasswordParameters { Password = string.Empty, IsHash = true };
var status = configuration.TryLoadSettings(uri, out settings, passwordParams);
if (status == LoadStatus.PasswordNeeded && currentPassword != default(string))
{
passwordParams.Password = currentPassword;
passwordParams.IsHash = true;
status = configuration.TryLoadSettings(uri, out settings, passwordParams);
}
for (int attempts = 0; attempts < 5 && status == LoadStatus.PasswordNeeded; attempts++)
{
var isLocalConfig = source == UriSource.AppData || source == UriSource.ProgramData;
var purpose = isLocalConfig ? PasswordRequestPurpose.LocalSettings : PasswordRequestPurpose.Settings;
var success = TryGetPassword(purpose, out var password);
if (success)
{
passwordParams.Password = password;
passwordParams.IsHash = false;
}
else
{
return null;
}
status = configuration.TryLoadSettings(uri, out settings, passwordParams);
}
return status;
}
private bool? TryConfigureClient(Uri uri, PasswordParameters passwordParams, string currentPassword = default(string))
{
var mustAuthenticate = IsRequiredToAuthenticateForClientConfiguration(passwordParams, currentPassword);
@ -395,16 +357,6 @@ namespace SafeExamBrowser.Runtime.Operations
}
}
private bool TryGetPassword(PasswordRequestPurpose purpose, out string password)
{
var args = new PasswordRequiredEventArgs { Purpose = purpose };
ActionRequired?.Invoke(args);
password = args.Password;
return args.Success;
}
private bool TryInitializeSettingsUri(out Uri uri, out UriSource source)
{
var isValidUri = false;
@ -461,14 +413,5 @@ namespace SafeExamBrowser.Runtime.Operations
break;
}
}
private enum UriSource
{
Undefined,
AppData,
CommandLine,
ProgramData,
Reconfiguration
}
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2020 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.Collections.Generic;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.Server.Contracts;
namespace SafeExamBrowser.Runtime.Operations.Events
{
internal class ExamSelectionEventArgs : ActionRequiredEventArgs
{
internal IEnumerable<Exam> Exams { get; set; }
internal Exam SelectedExam { get; set; }
internal ExamSelectionEventArgs(IEnumerable<Exam> exams)
{
Exams = exams;
}
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2020 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.Core.Contracts.OperationModel.Events;
namespace SafeExamBrowser.Runtime.Operations.Events
{
internal class ServerFailureEventArgs : ActionRequiredEventArgs
{
public bool Abort { get; set; }
public bool Fallback { get; set; }
public string Message { get; set; }
public bool Retry { get; set; }
public ServerFailureEventArgs(string message)
{
Message = message;
}
}
}

View file

@ -0,0 +1,204 @@
/*
* Copyright (c) 2020 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;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Runtime.Operations.Events;
using SafeExamBrowser.Server.Contracts;
using SafeExamBrowser.Settings;
namespace SafeExamBrowser.Runtime.Operations
{
internal class ServerOperation : ConfigurationBaseOperation
{
private readonly ILogger logger;
private readonly IServerProxy server;
public override event ActionRequiredEventHandler ActionRequired;
public override event StatusChangedEventHandler StatusChanged;
public ServerOperation(
string[] commandLineArgs,
IConfigurationRepository configuration,
ILogger logger,
SessionContext context,
IServerProxy server) : base(commandLineArgs, configuration, context)
{
this.logger = logger;
this.server = server;
}
public override OperationResult Perform()
{
var result = OperationResult.Success;
if (Context.Next.Settings.SessionMode == SessionMode.Server)
{
logger.Info("Initializing server...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServer);
var (abort, fallback, success) = TryPerformWithFallback(() => server.Connect(Context.Next.Settings.Server));
if (success)
{
(abort, fallback, success) = TryPerformWithFallback(() => server.GetAvailableExams(), out var exams);
if (success)
{
var exam = SelectExam(exams);
(abort, fallback, success) = TryPerformWithFallback(() => server.GetConfigurationFor(exam), out var uri);
if (success)
{
var status = TryLoadSettings(uri, UriSource.Server, out _, out var settings);
if (status == LoadStatus.Success)
{
Context.Next.Settings = settings;
result = OperationResult.Success;
}
else
{
result = OperationResult.Failed;
}
}
}
}
if (abort)
{
result = OperationResult.Aborted;
}
if (fallback)
{
result = OperationResult.Success;
}
}
return result;
}
public override OperationResult Repeat()
{
var result = Revert();
if (result == OperationResult.Success)
{
result = Perform();
}
return result;
}
public override OperationResult Revert()
{
var result = OperationResult.Failed;
if (Context.Current.Settings.SessionMode == SessionMode.Server)
{
logger.Info("Finalizing server...");
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServer);
var disconnect = server.Disconnect();
if (disconnect.Success)
{
result = OperationResult.Success;
}
else
{
result = OperationResult.Failed;
}
}
else
{
result = OperationResult.Success;
}
return result;
}
protected override void InvokeActionRequired(ActionRequiredEventArgs args)
{
ActionRequired?.Invoke(args);
}
private (bool abort, bool fallback, bool success) TryPerformWithFallback(Func<ServerResponse> request)
{
var abort = false;
var fallback = false;
var success = false;
while (!success)
{
var response = request();
success = response.Success;
if (!success && !Retry(response.Message, out abort, out fallback))
{
break;
}
}
return (abort, fallback, success);
}
private (bool abort, bool fallback, bool success) TryPerformWithFallback<T>(Func<ServerResponse<T>> request, out T value)
{
var abort = false;
var fallback = false;
var success = false;
value = default(T);
while (!success)
{
var response = request();
success = response.Success;
value = response.Value;
if (!success && !Retry(response.Message, out abort, out fallback))
{
break;
}
}
return (abort, fallback, success);
}
private bool Retry(string message, out bool abort, out bool fallback)
{
var args = new ServerFailureEventArgs(message);
ActionRequired?.Invoke(args);
abort = args.Abort;
fallback = args.Fallback;
return args.Retry;
}
private Exam SelectExam(IEnumerable<Exam> exams)
{
var args = new ExamSelectionEventArgs(exams);
ActionRequired?.Invoke(args);
return args.SelectedExam;
}
}
}

View file

@ -384,15 +384,31 @@ namespace SafeExamBrowser.Runtime
case ConfigurationCompletedEventArgs a:
AskIfConfigurationSufficient(a);
break;
case ExamSelectionEventArgs a:
AskForExamSelection(a);
break;
case MessageEventArgs m:
ShowMessageBox(m);
break;
case PasswordRequiredEventArgs p:
AskForPassword(p);
break;
case ServerFailureEventArgs a:
AskForServerFailureAction(a);
break;
}
}
private void AskForExamSelection(ExamSelectionEventArgs a)
{
// TODO: Also implement mechanism to retrieve selection via client!!
}
private void AskForServerFailureAction(ServerFailureEventArgs a)
{
// TODO: Also implement mechanism to retrieve selection via client!!
}
private void AskIfConfigurationSufficient(ConfigurationCompletedEventArgs args)
{
var message = TextKey.MessageBox_ClientConfigurationQuestion;

View file

@ -90,16 +90,20 @@
<Compile Include="App.cs" />
<Compile Include="Operations\ClientOperation.cs" />
<Compile Include="Operations\ClientTerminationOperation.cs" />
<Compile Include="Operations\ConfigurationBaseOperation.cs" />
<Compile Include="Operations\ConfigurationOperation.cs" />
<Compile Include="Operations\Events\ClientConfigurationErrorMessageArgs.cs" />
<Compile Include="Operations\Events\ConfigurationCompletedEventArgs.cs" />
<Compile Include="Operations\Events\ExamSelectionEventArgs.cs" />
<Compile Include="Operations\Events\InvalidDataMessageArgs.cs" />
<Compile Include="Operations\Events\InvalidPasswordMessageArgs.cs" />
<Compile Include="Operations\Events\MessageEventArgs.cs" />
<Compile Include="Operations\Events\NotSupportedMessageArgs.cs" />
<Compile Include="Operations\Events\PasswordRequiredEventArgs.cs" />
<Compile Include="Operations\Events\ServerFailureEventArgs.cs" />
<Compile Include="Operations\Events\UnexpectedErrorMessageArgs.cs" />
<Compile Include="Operations\KioskModeOperation.cs" />
<Compile Include="Operations\ServerOperation.cs" />
<Compile Include="Operations\ServiceOperation.cs" />
<Compile Include="Operations\SessionActivationOperation.cs" />
<Compile Include="Operations\SessionOperation.cs" />
@ -177,6 +181,14 @@
<Project>{e107026c-2011-4552-a7d8-3a0d37881df6}</Project>
<Name>SafeExamBrowser.Logging</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Server.Contracts\SafeExamBrowser.Server.Contracts.csproj">
<Project>{db701e6f-bddc-4cec-b662-335a9dc11809}</Project>
<Name>SafeExamBrowser.Server.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Server\SafeExamBrowser.Server.csproj">
<Project>{46edbde0-58b4-4725-9783-0c55c3d49c0c}</Project>
<Name>SafeExamBrowser.Server</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
<Name>SafeExamBrowser.Settings</Name>

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2020 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.Server.Contracts
{
/// <summary>
///
/// </summary>
public class Exam
{
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2020 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;
using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server.Contracts
{
/// <summary>
///
/// </summary>
public interface IServerProxy
{
/// <summary>
///
/// </summary>
ServerResponse Connect(ServerSettings settings);
/// <summary>
///
/// </summary>
ServerResponse Disconnect();
/// <summary>
///
/// </summary>
ServerResponse<IEnumerable<Exam>> GetAvailableExams();
/// <summary>
///
/// </summary>
ServerResponse<Uri> GetConfigurationFor(Exam exam);
/// <summary>
///
/// </summary>
ServerResponse SendSessionInfo(string sessionId);
}
}

View file

@ -54,7 +54,16 @@
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<Compile Include="Exam.cs" />
<Compile Include="IServerProxy.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServerResponse.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
<Name>SafeExamBrowser.Settings</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2020 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.Server.Contracts
{
/// <summary>
/// Defines the result of a communication with a SEB server.
/// </summary>
public class ServerResponse
{
/// <summary>
/// The message retrieved by the server in case the communication failed.
/// </summary>
public string Message { get; }
/// <summary>
/// Defines whether the communication was successful or not.
/// </summary>
public bool Success { get; }
public ServerResponse(bool success, string message = default(string))
{
Message = message;
Success = success;
}
}
/// <summary>
/// Defines the result of a communication with a SEB server.
/// </summary>
/// <typeparam name="T">The type of the expected response value.</typeparam>
public class ServerResponse<T> : ServerResponse
{
/// <summary>
/// The response value. Can be <c>null</c> or <c>default(T)</c> in case the communication failed!
/// </summary>
public T Value { get; }
public ServerResponse(bool success, T value, string message = default(string)) : base(success, message)
{
Value = value;
}
}
}

View file

@ -55,6 +55,17 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServerProxy.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SafeExamBrowser.Server.Contracts\SafeExamBrowser.Server.Contracts.csproj">
<Project>{db701e6f-bddc-4cec-b662-335a9dc11809}</Project>
<Name>SafeExamBrowser.Server.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
<Name>SafeExamBrowser.Settings</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2020 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;
using SafeExamBrowser.Server.Contracts;
using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server
{
public class ServerProxy : IServerProxy
{
public ServerResponse Connect(ServerSettings settings)
{
throw new NotImplementedException();
}
public ServerResponse Disconnect()
{
throw new NotImplementedException();
}
public ServerResponse<IEnumerable<Exam>> GetAvailableExams()
{
throw new NotImplementedException();
}
public ServerResponse<Uri> GetConfigurationFor(Exam exam)
{
throw new NotImplementedException();
}
public ServerResponse SendSessionInfo(string sessionId)
{
throw new NotImplementedException();
}
}
}