SEBWIN-405: Implemented basic server binding up to exam selection.
This commit is contained in:
parent
0edca494b3
commit
c2cd3a742f
24 changed files with 663 additions and 28 deletions
|
@ -23,6 +23,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
|
|||
new GeneralDataMapper(),
|
||||
new InputDataMapper(),
|
||||
new SecurityDataMapper(),
|
||||
new ServerDataMapper(),
|
||||
new ServiceDataMapper(),
|
||||
new UserInterfaceDataMapper()
|
||||
};
|
||||
|
|
|
@ -31,6 +31,10 @@ namespace SafeExamBrowser.I18n.Contracts
|
|||
BrowserWindow_DownloadComplete,
|
||||
BrowserWindow_ZoomMenuItem,
|
||||
Build,
|
||||
ExamSelectionDialog_Cancel,
|
||||
ExamSelectionDialog_Message,
|
||||
ExamSelectionDialog_Select,
|
||||
ExamSelectionDialog_Title,
|
||||
FileSystemDialog_Cancel,
|
||||
FileSystemDialog_LoadError,
|
||||
FileSystemDialog_Loading,
|
||||
|
|
|
@ -51,6 +51,18 @@
|
|||
<Entry key="Build">
|
||||
Build
|
||||
</Entry>
|
||||
<Entry key="ExamSelectionDialog_Cancel">
|
||||
Abbrechen
|
||||
</Entry>
|
||||
<Entry key="ExamSelectionDialog_Message">
|
||||
Bitte wählen Sie eine der verfügbaren SEB-Server-Prüfungen:
|
||||
</Entry>
|
||||
<Entry key="ExamSelectionDialog_Select">
|
||||
Auswählen
|
||||
</Entry>
|
||||
<Entry key="ExamSelectionDialog_Title">
|
||||
SEB-Server-Prüfungen
|
||||
</Entry>
|
||||
<Entry key="FileSystemDialog_Cancel">
|
||||
Abbrechen
|
||||
</Entry>
|
||||
|
|
|
@ -51,6 +51,18 @@
|
|||
<Entry key="Build">
|
||||
Build
|
||||
</Entry>
|
||||
<Entry key="ExamSelectionDialog_Cancel">
|
||||
Cancel
|
||||
</Entry>
|
||||
<Entry key="ExamSelectionDialog_Message">
|
||||
Please select one of the available SEB-Server exams:
|
||||
</Entry>
|
||||
<Entry key="ExamSelectionDialog_Select">
|
||||
Select
|
||||
</Entry>
|
||||
<Entry key="ExamSelectionDialog_Title">
|
||||
SEB-Server Exams
|
||||
</Entry>
|
||||
<Entry key="FileSystemDialog_Cancel">
|
||||
Cancel
|
||||
</Entry>
|
||||
|
|
|
@ -67,7 +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 server = new ServerProxy(ModuleLogger(nameof(ServerProxy)));
|
||||
var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), ModuleLogger(nameof(ServiceProxy)), Interlocutor.Runtime);
|
||||
var sessionContext = new SessionContext();
|
||||
var splashScreen = uiFactory.CreateSplashScreen(appConfig);
|
||||
|
|
|
@ -16,6 +16,7 @@ namespace SafeExamBrowser.Runtime.Operations.Events
|
|||
{
|
||||
internal IEnumerable<Exam> Exams { get; set; }
|
||||
internal Exam SelectedExam { get; set; }
|
||||
internal bool Success { get; set; }
|
||||
|
||||
internal ExamSelectionEventArgs(IEnumerable<Exam> exams)
|
||||
{
|
||||
|
|
|
@ -47,7 +47,9 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
logger.Info("Initializing server...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServer);
|
||||
|
||||
var (abort, fallback, success) = TryPerformWithFallback(() => server.Connect(Context.Next.Settings.Server));
|
||||
server.Initialize(Context.Next.Settings.Server);
|
||||
|
||||
var (abort, fallback, success) = TryPerformWithFallback(() => server.Connect(), out var token);
|
||||
|
||||
if (success)
|
||||
{
|
||||
|
@ -55,24 +57,32 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
|
||||
if (success)
|
||||
{
|
||||
var exam = SelectExam(exams);
|
||||
|
||||
(abort, fallback, success) = TryPerformWithFallback(() => server.GetConfigurationFor(exam), out var uri);
|
||||
success = TrySelectExam(exams, out var exam);
|
||||
|
||||
if (success)
|
||||
{
|
||||
var status = TryLoadSettings(uri, UriSource.Server, out _, out var settings);
|
||||
(abort, fallback, success) = TryPerformWithFallback(() => server.GetConfigurationFor(exam), out var uri);
|
||||
|
||||
if (status == LoadStatus.Success)
|
||||
if (success)
|
||||
{
|
||||
Context.Next.Settings = settings;
|
||||
result = OperationResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = OperationResult.Failed;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info("The user aborted the exam selection.");
|
||||
result = OperationResult.Aborted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,13 +202,14 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
return args.Retry;
|
||||
}
|
||||
|
||||
private Exam SelectExam(IEnumerable<Exam> exams)
|
||||
private bool TrySelectExam(IEnumerable<Exam> exams, out Exam exam)
|
||||
{
|
||||
var args = new ExamSelectionEventArgs(exams);
|
||||
|
||||
ActionRequired?.Invoke(args);
|
||||
exam = args.SelectedExam;
|
||||
|
||||
return args.SelectedExam;
|
||||
return args.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -399,12 +399,23 @@ namespace SafeExamBrowser.Runtime
|
|||
}
|
||||
}
|
||||
|
||||
private void AskForExamSelection(ExamSelectionEventArgs a)
|
||||
private void AskForExamSelection(ExamSelectionEventArgs args)
|
||||
{
|
||||
// TODO: Also implement mechanism to retrieve selection via client!!
|
||||
var isStartup = !SessionIsRunning;
|
||||
var isRunningOnDefaultDesktop = SessionIsRunning && Session.Settings.Security.KioskMode == KioskMode.DisableExplorerShell;
|
||||
|
||||
if (isStartup || isRunningOnDefaultDesktop)
|
||||
{
|
||||
TryAskForExamSelectionViaDialog(args);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Also implement mechanism to retrieve selection via client!!
|
||||
// TryAskForExamSelectionViaClient(args);
|
||||
}
|
||||
}
|
||||
|
||||
private void AskForServerFailureAction(ServerFailureEventArgs a)
|
||||
private void AskForServerFailureAction(ServerFailureEventArgs args)
|
||||
{
|
||||
// TODO: Also implement mechanism to retrieve selection via client!!
|
||||
}
|
||||
|
@ -490,6 +501,17 @@ namespace SafeExamBrowser.Runtime
|
|||
return result;
|
||||
}
|
||||
|
||||
private void TryAskForExamSelectionViaDialog(ExamSelectionEventArgs args)
|
||||
{
|
||||
var message = TextKey.ExamSelectionDialog_Message;
|
||||
var title = TextKey.ExamSelectionDialog_Title;
|
||||
var dialog = uiFactory.CreateExamSelectionDialog(text.Get(message), text.Get(title), args.Exams);
|
||||
var result = dialog.Show(runtimeWindow);
|
||||
|
||||
args.SelectedExam = result.SelectedExam;
|
||||
args.Success = result.Success;
|
||||
}
|
||||
|
||||
private void TryGetPasswordViaDialog(PasswordRequiredEventArgs args)
|
||||
{
|
||||
var message = default(TextKey);
|
||||
|
|
|
@ -9,10 +9,23 @@
|
|||
namespace SafeExamBrowser.Server.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// Defines a server exam.
|
||||
/// </summary>
|
||||
public class Exam
|
||||
{
|
||||
/// <summary>
|
||||
/// The identifier of the exam.
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the exam.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the exam.
|
||||
/// </summary>
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,14 +13,15 @@ using SafeExamBrowser.Settings.Server;
|
|||
namespace SafeExamBrowser.Server.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// Defines the communication options with a server.
|
||||
/// </summary>
|
||||
public interface IServerProxy
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// TODO: Return API as well or re-load in proxy instance of client?
|
||||
/// Attempts to initialize a connection to the server. If successful, returns a OAuth2 token as response value.
|
||||
/// </summary>
|
||||
ServerResponse Connect(ServerSettings settings);
|
||||
ServerResponse<string> Connect();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
|
@ -37,6 +38,11 @@ namespace SafeExamBrowser.Server.Contracts
|
|||
/// </summary>
|
||||
ServerResponse<Uri> GetConfigurationFor(Exam exam);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the server settings to be used for communication.
|
||||
/// </summary>
|
||||
void Initialize(ServerSettings settings);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
|
|
23
SafeExamBrowser.Server/Data/ApiVersion1.cs
Normal file
23
SafeExamBrowser.Server/Data/ApiVersion1.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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.Data
|
||||
{
|
||||
internal class ApiVersion1
|
||||
{
|
||||
public string AccessTokenEndpoint { get; set; }
|
||||
|
||||
public string HandshakeEndpoint { get; set; }
|
||||
|
||||
public string ConfigurationEndpoint { get; set; }
|
||||
|
||||
public string PingEndpoint { get; set; }
|
||||
|
||||
public string LogEndpoint { get; set; }
|
||||
}
|
||||
}
|
|
@ -50,14 +50,23 @@
|
|||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Data\ApiVersion1.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ServerProxy.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Logging.Contracts\SafeExamBrowser.Logging.Contracts.csproj">
|
||||
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
||||
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Server.Contracts\SafeExamBrowser.Server.Contracts.csproj">
|
||||
<Project>{db701e6f-bddc-4cec-b662-335a9dc11809}</Project>
|
||||
<Name>SafeExamBrowser.Server.Contracts</Name>
|
||||
|
@ -67,5 +76,8 @@
|
|||
<Name>SafeExamBrowser.Settings</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -8,36 +8,313 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts;
|
||||
using SafeExamBrowser.Server.Data;
|
||||
using SafeExamBrowser.Settings.Server;
|
||||
|
||||
namespace SafeExamBrowser.Server
|
||||
{
|
||||
public class ServerProxy : IServerProxy
|
||||
{
|
||||
public ServerResponse Connect(ServerSettings settings)
|
||||
private ApiVersion1 api;
|
||||
private string connectionToken;
|
||||
private HttpClient httpClient;
|
||||
private ILogger logger;
|
||||
private string oauth2Token;
|
||||
private ServerSettings settings;
|
||||
|
||||
public ServerProxy(ILogger logger)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
this.api = new ApiVersion1();
|
||||
this.httpClient = new HttpClient();
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public ServerResponse<string> Connect()
|
||||
{
|
||||
var success = TryExecute(HttpMethod.Get, settings.ApiUrl, out var response);
|
||||
var message = ToString(response);
|
||||
|
||||
if (success && TryParseApi(response.Content))
|
||||
{
|
||||
logger.Info("Successfully loaded server API.");
|
||||
|
||||
var secret = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{settings.ClientName}:{settings.ClientSecret}"));
|
||||
var authorization = ("Authorization", $"Basic {secret}");
|
||||
var content = "grant_type=client_credentials&scope=read write";
|
||||
var contentType = "application/x-www-form-urlencoded";
|
||||
|
||||
success = TryExecute(HttpMethod.Post, api.AccessTokenEndpoint, out response, content, contentType, authorization);
|
||||
message = ToString(response);
|
||||
|
||||
if (success && TryParseOauth2Token(response.Content))
|
||||
{
|
||||
logger.Info("Successfully retrieved OAuth2 token.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to retrieve OAuth2 token!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to load server API!");
|
||||
}
|
||||
|
||||
return new ServerResponse<string>(success, oauth2Token, message);
|
||||
}
|
||||
|
||||
public ServerResponse Disconnect()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return new ServerResponse(false, "Some error message here");
|
||||
}
|
||||
|
||||
public ServerResponse<IEnumerable<Exam>> GetAvailableExams()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var authorization = ("Authorization", $"Bearer {oauth2Token}");
|
||||
var content = $"institutionId={settings.Institution}";
|
||||
var contentType = "application/x-www-form-urlencoded";
|
||||
|
||||
var success = TryExecute(HttpMethod.Post, api.HandshakeEndpoint, out var response, content, contentType, authorization);
|
||||
var message = ToString(response);
|
||||
var hasToken = TryParseConnectionToken(response);
|
||||
var hasExams = TryParseExams(response.Content, out var exams);
|
||||
|
||||
if (success && hasExams && hasToken)
|
||||
{
|
||||
logger.Info("Successfully retrieved connection token and available exams.");
|
||||
}
|
||||
else if (!hasExams)
|
||||
{
|
||||
logger.Error("Failed to retrieve available exams!");
|
||||
}
|
||||
else if (!hasToken)
|
||||
{
|
||||
logger.Error("Failed to retrieve connection token!");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to load connection token and available exams!");
|
||||
}
|
||||
|
||||
return new ServerResponse<IEnumerable<Exam>>(hasExams && hasToken, exams, message);
|
||||
}
|
||||
|
||||
public ServerResponse<Uri> GetConfigurationFor(Exam exam)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// 4. Send exam ID
|
||||
|
||||
return new ServerResponse<Uri>(false, default(Uri), "Some error message here");
|
||||
}
|
||||
|
||||
public void Initialize(ServerSettings settings)
|
||||
{
|
||||
this.settings = settings;
|
||||
httpClient.BaseAddress = new Uri(settings.ServerUrl);
|
||||
|
||||
if (settings.RequestTimeout > 0)
|
||||
{
|
||||
httpClient.Timeout = TimeSpan.FromMilliseconds(settings.RequestTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
public ServerResponse SendSessionInfo(string sessionId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return new ServerResponse(false, "Some error message here");
|
||||
}
|
||||
|
||||
private bool TryParseApi(HttpContent content)
|
||||
{
|
||||
var success = false;
|
||||
|
||||
try
|
||||
{
|
||||
var json = JsonConvert.DeserializeObject(Extract(content)) as JObject;
|
||||
var apis = json["api-versions"];
|
||||
|
||||
foreach (var api in apis.AsJEnumerable())
|
||||
{
|
||||
if (api["name"].Value<string>().Equals("v1"))
|
||||
{
|
||||
foreach (var endpoint in api["endpoints"].AsJEnumerable())
|
||||
{
|
||||
var name = endpoint["name"].Value<string>();
|
||||
var location = endpoint["location"].Value<string>();
|
||||
|
||||
switch (name)
|
||||
{
|
||||
case "access-token-endpoint":
|
||||
this.api.AccessTokenEndpoint = location;
|
||||
break;
|
||||
case "seb-configuration-endpoint":
|
||||
this.api.ConfigurationEndpoint = location;
|
||||
break;
|
||||
case "seb-handshake-endpoint":
|
||||
this.api.HandshakeEndpoint = location;
|
||||
break;
|
||||
case "seb-log-endpoint":
|
||||
this.api.LogEndpoint = location;
|
||||
break;
|
||||
case "seb-ping-endpoint":
|
||||
this.api.PingEndpoint = location;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
success = true;
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
logger.Error("The selected SEB server instance does not support the required API version!");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse server API!", e);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TryParseConnectionToken(HttpResponseMessage response)
|
||||
{
|
||||
try
|
||||
{
|
||||
var hasHeader = response.Headers.TryGetValues("SEBConnectionToken", out var values);
|
||||
|
||||
if (hasHeader)
|
||||
{
|
||||
connectionToken = values.First();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to retrieve connection token!");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse connection token!", e);
|
||||
}
|
||||
|
||||
return connectionToken != default(string);
|
||||
}
|
||||
|
||||
private bool TryParseExams(HttpContent content, out IList<Exam> exams)
|
||||
{
|
||||
exams = new List<Exam>();
|
||||
|
||||
try
|
||||
{
|
||||
var json = JsonConvert.DeserializeObject(Extract(content)) as JArray;
|
||||
|
||||
foreach (var exam in json.AsJEnumerable())
|
||||
{
|
||||
exams.Add(new Exam
|
||||
{
|
||||
Id = exam["examId"].Value<string>(),
|
||||
Name = exam["name"].Value<string>(),
|
||||
Url = exam["url"].Value<string>()
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse exams!", e);
|
||||
}
|
||||
|
||||
return exams.Any();
|
||||
}
|
||||
|
||||
private bool TryParseOauth2Token(HttpContent content)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonConvert.DeserializeObject(Extract(content)) as JObject;
|
||||
|
||||
oauth2Token = json["access_token"].Value<string>();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse Oauth2 token!", e);
|
||||
}
|
||||
|
||||
return oauth2Token != default(string);
|
||||
}
|
||||
|
||||
private bool TryExecute(
|
||||
HttpMethod method,
|
||||
string url,
|
||||
out HttpResponseMessage response,
|
||||
string content = default(string),
|
||||
string contentType = default(string),
|
||||
params (string name, string value)[] headers)
|
||||
{
|
||||
response = default(HttpResponseMessage);
|
||||
|
||||
for (var attempt = 0; attempt < settings.RequestAttempts && response == default(HttpResponseMessage); attempt++)
|
||||
{
|
||||
var request = new HttpRequestMessage(method, url);
|
||||
|
||||
if (content != default(string))
|
||||
{
|
||||
request.Content = new StringContent(content, Encoding.UTF8);
|
||||
|
||||
if (contentType != default(string))
|
||||
{
|
||||
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (name, value) in headers)
|
||||
{
|
||||
request.Headers.Add(name, value);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
response = httpClient.SendAsync(request).GetAwaiter().GetResult();
|
||||
logger.Debug($"Request was successful: {request.Method} '{request.RequestUri}' -> {ToString(response)}");
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
logger.Error($"Request {request.Method} '{request.RequestUri}' did not complete within {settings.RequestTimeout}ms!");
|
||||
break;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Debug($"Request {request.Method} '{request.RequestUri}' failed due to {e}");
|
||||
}
|
||||
}
|
||||
|
||||
return response != default(HttpResponseMessage) && response.IsSuccessStatusCode;
|
||||
}
|
||||
|
||||
private string Extract(HttpContent content)
|
||||
{
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
return await content.ReadAsStreamAsync();
|
||||
});
|
||||
var stream = task.GetAwaiter().GetResult();
|
||||
var reader = new StreamReader(stream);
|
||||
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
|
||||
private string ToString(HttpResponseMessage response)
|
||||
{
|
||||
return $"{(int) response.StatusCode} {response.StatusCode} {response.ReasonPhrase}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
4
SafeExamBrowser.Server/packages.config
Normal file
4
SafeExamBrowser.Server/packages.config
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="12.0.3" targetFramework="net472" />
|
||||
</packages>
|
|
@ -12,6 +12,7 @@ using SafeExamBrowser.Client.Contracts;
|
|||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Audio;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
||||
|
@ -54,6 +55,11 @@ namespace SafeExamBrowser.UserInterface.Contracts
|
|||
/// </summary>
|
||||
IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings, bool isMainWindow);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an exam selection dialog for the given exams.
|
||||
/// </summary>
|
||||
IExamSelectionDialog CreateExamSelectionDialog(string message, string title, IEnumerable<Exam> exams);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a system control which allows to change the keyboard layout of the computer.
|
||||
/// </summary>
|
||||
|
|
|
@ -86,9 +86,11 @@
|
|||
<Compile Include="Shell\ITaskviewActivator.cs" />
|
||||
<Compile Include="Shell\ITerminationActivator.cs" />
|
||||
<Compile Include="Shell\Location.cs" />
|
||||
<Compile Include="Windows\Data\ExamSelectionDialogResult.cs" />
|
||||
<Compile Include="Windows\Data\LockScreenOption.cs" />
|
||||
<Compile Include="Windows\Data\LockScreenResult.cs" />
|
||||
<Compile Include="Windows\Events\WindowClosingEventHandler.cs" />
|
||||
<Compile Include="Windows\IExamSelectionDialog.cs" />
|
||||
<Compile Include="Windows\ILockScreen.cs" />
|
||||
<Compile Include="Windows\IPasswordDialog.cs" />
|
||||
<Compile Include="Windows\Data\PasswordDialogResult.cs" />
|
||||
|
@ -117,6 +119,10 @@
|
|||
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
||||
<Name>SafeExamBrowser.Logging.Contracts</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.Settings\SafeExamBrowser.Settings.csproj">
|
||||
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
|
||||
<Name>SafeExamBrowser.Settings</Name>
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.Server.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Contracts.Windows.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the user interaction result of an <see cref="IExamSelectionDialog"/>.
|
||||
/// </summary>
|
||||
public class ExamSelectionDialogResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The exam selected by the user.
|
||||
/// </summary>
|
||||
public Exam SelectedExam { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the user confirmed the dialog or not.
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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.UserInterface.Contracts.Windows.Data;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Contracts.Windows
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the functionality of an exam selection dialog.
|
||||
/// </summary>
|
||||
public interface IExamSelectionDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Shows the dialog as topmost window. If a parent window is specified, the dialog is rendered modally for the given parent.
|
||||
/// </summary>
|
||||
ExamSelectionDialogResult Show(IWindow parent = null);
|
||||
}
|
||||
}
|
|
@ -148,6 +148,9 @@
|
|||
<Compile Include="Controls\Taskview\WindowControl.xaml.cs">
|
||||
<DependentUpon>WindowControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Windows\ExamSelectionDialog.xaml.cs">
|
||||
<DependentUpon>ExamSelectionDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Windows\FileSystemDialog.xaml.cs">
|
||||
<DependentUpon>FileSystemDialog.xaml</DependentUpon>
|
||||
</Compile>
|
||||
|
@ -332,6 +335,10 @@
|
|||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Windows\ExamSelectionDialog.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Windows\FileSystemDialog.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
@ -474,6 +481,10 @@
|
|||
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
||||
<Name>SafeExamBrowser.Logging.Contracts</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.Settings\SafeExamBrowser.Settings.csproj">
|
||||
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
|
||||
<Name>SafeExamBrowser.Settings</Name>
|
||||
|
|
|
@ -16,6 +16,7 @@ using SafeExamBrowser.Client.Contracts;
|
|||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Audio;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
||||
|
@ -81,6 +82,11 @@ namespace SafeExamBrowser.UserInterface.Desktop
|
|||
return Application.Current.Dispatcher.Invoke(() => new BrowserWindow(control, settings, isMainWindow, text));
|
||||
}
|
||||
|
||||
public IExamSelectionDialog CreateExamSelectionDialog(string message, string title, IEnumerable<Exam> exams)
|
||||
{
|
||||
return Application.Current.Dispatcher.Invoke(() => new ExamSelectionDialog(message, title, text, exams));
|
||||
}
|
||||
|
||||
public ISystemControl CreateKeyboardLayoutControl(IKeyboard keyboard, Location location)
|
||||
{
|
||||
if (location == Location.ActionCenter)
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
<Window x:Class="SafeExamBrowser.UserInterface.Desktop.Windows.ExamSelectionDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:fa="http://schemas.fontawesome.io/icons/"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Windows"
|
||||
mc:Ignorable="d" Height="350" Width="600" ResizeMode="NoResize" Topmost="True">
|
||||
<Window.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Templates/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid FocusManager.FocusedElement="{Binding ElementName=ExamList}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="4*" />
|
||||
<RowDefinition Height="1*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<fa:ImageAwesome Grid.Column="0" Foreground="LightGray" Icon="PencilSquareOutline" Margin="25" Width="50" />
|
||||
<Grid Grid.Column="1" Margin="0,0,25,25">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" x:Name="Message" Margin="0,10" TextWrapping="WrapWithOverflow" VerticalAlignment="Bottom" />
|
||||
<ListBox Grid.Row="1" x:Name="ExamList" Cursor="Hand">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock FontWeight="Bold" Text="{Binding Name}" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Margin="0,0,5,0" Text="{Binding Id}" />
|
||||
<TextBlock Margin="0,0,5,0" Text="-" />
|
||||
<TextBlock FontStyle="Italic" Text="{Binding Url}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid Grid.Row="1" Background="{StaticResource BackgroundBrush}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<WrapPanel Orientation="Horizontal" Margin="25,0" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Button x:Name="SelectButton" Cursor="Hand" Margin="10,0" Padding="10,5" MinWidth="75" IsEnabled="False" />
|
||||
<Button x:Name="CancelButton" Cursor="Hand" Padding="10,5" MinWidth="75" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
|
||||
|
||||
namespace SafeExamBrowser.UserInterface.Desktop.Windows
|
||||
{
|
||||
public partial class ExamSelectionDialog : Window, IExamSelectionDialog
|
||||
{
|
||||
private readonly IText text;
|
||||
|
||||
public ExamSelectionDialog(string message, string title, IText text, IEnumerable<Exam> exams)
|
||||
{
|
||||
this.text = text;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeExamSelectionDialog(message, title, exams);
|
||||
}
|
||||
|
||||
public ExamSelectionDialogResult Show(IWindow parent = null)
|
||||
{
|
||||
return Dispatcher.Invoke(() =>
|
||||
{
|
||||
var result = new ExamSelectionDialogResult { Success = false };
|
||||
|
||||
if (parent is Window)
|
||||
{
|
||||
Owner = parent as Window;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
}
|
||||
|
||||
if (ShowDialog() is true)
|
||||
{
|
||||
result.SelectedExam = ExamList.SelectedItem as Exam;
|
||||
result.Success = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializeExamSelectionDialog(string message, string title, IEnumerable<Exam> exams)
|
||||
{
|
||||
Message.Text = message;
|
||||
Title = title;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
||||
|
||||
CancelButton.Content = text.Get(TextKey.ExamSelectionDialog_Cancel);
|
||||
CancelButton.Click += CancelButton_Click;
|
||||
|
||||
SelectButton.Content = text.Get(TextKey.ExamSelectionDialog_Select);
|
||||
SelectButton.Click += ConfirmButton_Click;
|
||||
|
||||
ExamList.ItemsSource = exams;
|
||||
ExamList.SelectionChanged += ExamList_SelectionChanged;
|
||||
|
||||
Loaded += (o, args) => Activate();
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void ExamList_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
SelectButton.IsEnabled = ExamList.SelectedItem != null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -208,6 +208,10 @@
|
|||
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
||||
<Name>SafeExamBrowser.Logging.Contracts</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.Settings\SafeExamBrowser.Settings.csproj">
|
||||
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
|
||||
<Name>SafeExamBrowser.Settings</Name>
|
||||
|
|
|
@ -16,6 +16,7 @@ using SafeExamBrowser.Client.Contracts;
|
|||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Audio;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
|
||||
|
@ -81,6 +82,12 @@ namespace SafeExamBrowser.UserInterface.Mobile
|
|||
return Application.Current.Dispatcher.Invoke(() => new BrowserWindow(control, settings, isMainWindow, text));
|
||||
}
|
||||
|
||||
public IExamSelectionDialog CreateExamSelectionDialog(string message, string title, IEnumerable<Exam> exams)
|
||||
{
|
||||
// TODO
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public ISystemControl CreateKeyboardLayoutControl(IKeyboard keyboard, Location location)
|
||||
{
|
||||
if (location == Location.ActionCenter)
|
||||
|
|
Loading…
Add table
Reference in a new issue