Restore SEBPatch

This commit is contained in:
2025-06-01 11:56:28 +02:00
parent 8c656e3137
commit 00707825b4
1009 changed files with 5005 additions and 6502 deletions

View File

@@ -1,19 +0,0 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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; }
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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

View File

@@ -1,58 +0,0 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.Net.Http;
using System.Text;
using SafeExamBrowser.Settings.Logging;
namespace SafeExamBrowser.Server
{
internal static class Extensions
{
internal static string ToLogString(this HttpResponseMessage response)
{
return response == default ? "No Response" : $"{(int) response.StatusCode} {response.StatusCode} {response.ReasonPhrase}";
}
internal static string ToLogType(this LogLevel severity)
{
switch (severity)
{
case LogLevel.Debug:
return "DEBUG_LOG";
case LogLevel.Error:
return "ERROR_LOG";
case LogLevel.Info:
return "INFO_LOG";
case LogLevel.Warning:
return "WARN_LOG";
}
return "UNKNOWN";
}
internal static string ToSummary(this Exception exception)
{
var trimChars = new[] { '.', '!' };
var summary = new StringBuilder(exception.Message?.TrimEnd(trimChars));
for (var inner = exception.InnerException; inner != default; inner = inner.InnerException)
{
summary.Append($" -> {inner.Message?.TrimEnd(trimChars)}");
}
return summary.ToString();
}
internal static long ToUnixTimestamp(this DateTime date)
{
return new DateTimeOffset(date).ToUnixTimeMilliseconds();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -50,11 +50,11 @@ namespace SafeExamBrowser.Server
return isExpired;
}
internal bool TryParseApi(HttpContent content, out ApiVersion1 api)
internal bool TryParseApi(HttpContent content, out Api api)
{
var success = false;
api = new ApiVersion1();
api = new Api();
try
{
@@ -343,7 +343,7 @@ namespace SafeExamBrowser.Server
private ScreenProctoringInstruction ParseScreenProctoringInstruction(JObject attributesJson)
{
return new ScreenProctoringInstruction
var instruction = new ScreenProctoringInstruction
{
ClientId = attributesJson["screenProctoringClientId"].Value<string>(),
ClientSecret = attributesJson["screenProctoringClientSecret"].Value<string>(),
@@ -351,6 +351,13 @@ namespace SafeExamBrowser.Server
ServiceUrl = attributesJson["screenProctoringServiceURL"].Value<string>(),
SessionId = attributesJson["screenProctoringClientSessionId"].Value<string>()
};
if (attributesJson.ContainsKey("screenProctoringEncryptSecret"))
{
instruction.EncryptionSecret = attributesJson["screenProctoringEncryptSecret"].Value<string>();
}
return instruction;
}
private ZoomInstruction ParseZoomInstruction(JObject attributesJson)

View File

@@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Server")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
[assembly: AssemblyCopyright("Copyright © 2025 ETH Zürich, IT Services")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -13,27 +13,32 @@ using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server.Requests
{
internal class ApiRequest : BaseRequest
internal class ApiRequest : Request
{
private readonly Sanitizer sanitizer;
internal ApiRequest(
ApiVersion1 api,
Api api,
HttpClient httpClient,
ILogger logger,
Parser parser,
Sanitizer sanitizer,
ServerSettings settings) : base(api, httpClient, logger, parser, settings)
{
this.sanitizer = sanitizer;
}
internal bool TryExecute(out ApiVersion1 api, out string message)
internal bool TryExecute(out Api api, out string message)
{
var success = TryExecute(HttpMethod.Get, settings.ApiUrl, out var response);
api = new ApiVersion1();
api = new Api();
message = response.ToLogString();
if (success)
{
parser.TryParseApi(response.Content, out api);
sanitizer.Sanitize(api);
}
return success;

View File

@@ -1,37 +0,0 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.Net.Http;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Server.Data;
using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server.Requests
{
internal class AppSignatureKeyRequest : BaseRequest
{
internal AppSignatureKeyRequest(
ApiVersion1 api,
HttpClient httpClient,
ILogger logger,
Parser parser,
ServerSettings settings) : base(api, httpClient, logger, parser, settings)
{
}
internal bool TryExecute(string appSignatureKey, out string message)
{
var content = $"seb_signature_key={appSignatureKey}";
var success = TryExecute(new HttpMethod("PATCH"), api.HandshakeEndpoint, out var response, content, ContentType.URL_ENCODED, Authorization, Token);
message = response.ToLogString();
return success;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -17,14 +17,14 @@ using SafeExamBrowser.SystemComponents.Contracts;
namespace SafeExamBrowser.Server.Requests
{
internal class AvailableExamsRequest : BaseRequest
internal class AvailableExamsRequest : Request
{
private readonly AppConfig appConfig;
private readonly ISystemInfo systemInfo;
private readonly IUserInfo userInfo;
internal AvailableExamsRequest(
ApiVersion1 api,
Api api,
AppConfig appConfig,
HttpClient httpClient,
ILogger logger,

View File

@@ -1,213 +0,0 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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 System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Server.Data;
using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server.Requests
{
internal abstract class BaseRequest
{
private static string connectionToken;
private static string oauth2Token;
private readonly HttpClient httpClient;
private bool hadException;
protected readonly ApiVersion1 api;
protected readonly ILogger logger;
protected readonly Parser parser;
protected readonly ServerSettings settings;
protected (string, string) Authorization => (Header.AUTHORIZATION, $"Bearer {oauth2Token}");
protected (string, string) Token => (Header.CONNECTION_TOKEN, connectionToken);
internal static string ConnectionToken
{
get { return connectionToken; }
set { connectionToken = value; }
}
internal static string Oauth2Token
{
get { return oauth2Token; }
set { oauth2Token = value; }
}
protected BaseRequest(ApiVersion1 api, HttpClient httpClient, ILogger logger, Parser parser, ServerSettings settings)
{
this.api = api;
this.httpClient = httpClient;
this.logger = logger;
this.parser = parser;
this.settings = settings;
}
protected bool TryExecute(
HttpMethod method,
string url,
out HttpResponseMessage response,
string content = default,
string contentType = default,
params (string name, string value)[] headers)
{
response = default;
for (var attempt = 0; attempt < settings.RequestAttempts && (response == default || !response.IsSuccessStatusCode); attempt++)
{
var request = BuildRequest(method, url, content, contentType, headers);
try
{
response = httpClient.SendAsync(request).GetAwaiter().GetResult();
if (PerformLoggingFor(request))
{
logger.Debug($"Completed request: {request.Method} '{request.RequestUri}' -> {response.ToLogString()}");
}
if (response.StatusCode == HttpStatusCode.Unauthorized && parser.IsTokenExpired(response.Content))
{
logger.Info("OAuth2 token has expired, attempting to retrieve new one...");
if (TryRetrieveOAuth2Token(out var message))
{
headers = UpdateOAuth2Token(headers);
}
}
}
catch (TaskCanceledException)
{
if (PerformLoggingFor(request))
{
logger.Warn($"Request {request.Method} '{request.RequestUri}' did not complete within {settings.RequestTimeout}ms!");
}
break;
}
catch (Exception e)
{
if (PerformLoggingFor(request) && IsFirstException())
{
logger.Warn($"Request {request.Method} '{request.RequestUri}' has failed: {e.ToSummary()}!");
}
}
}
return response != default && response.IsSuccessStatusCode;
}
protected bool TryRetrieveConnectionToken(HttpResponseMessage response)
{
var success = parser.TryParseConnectionToken(response, out connectionToken);
if (success)
{
logger.Info("Successfully retrieved connection token.");
}
else
{
logger.Error("Failed to retrieve connection token!");
}
return success;
}
protected bool TryRetrieveOAuth2Token(out string message)
{
var secret = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{settings.ClientName}:{settings.ClientSecret}"));
var authorization = (Header.AUTHORIZATION, $"Basic {secret}");
var content = "grant_type=client_credentials&scope=read write";
var success = TryExecute(HttpMethod.Post, api.AccessTokenEndpoint, out var response, content, ContentType.URL_ENCODED, authorization);
message = response.ToLogString();
if (success && parser.TryParseOauth2Token(response.Content, out oauth2Token))
{
logger.Info("Successfully retrieved OAuth2 token.");
}
else
{
logger.Error("Failed to retrieve OAuth2 token!");
}
return success;
}
private HttpRequestMessage BuildRequest(
HttpMethod method,
string url,
string content = default,
string contentType = default,
params (string name, string value)[] headers)
{
var request = new HttpRequestMessage(method, url);
if (content != default)
{
request.Content = new StringContent(content, Encoding.UTF8);
if (contentType != default)
{
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType);
}
}
request.Headers.Add(Header.ACCEPT, "application/json, */*");
foreach (var (name, value) in headers)
{
request.Headers.Add(name, value);
}
return request;
}
private bool IsFirstException()
{
var isFirst = !hadException;
hadException = true;
return isFirst;
}
private bool PerformLoggingFor(HttpRequestMessage request)
{
return request.RequestUri.AbsolutePath != api.LogEndpoint && request.RequestUri.AbsolutePath != api.PingEndpoint;
}
private (string name, string value)[] UpdateOAuth2Token((string name, string value)[] headers)
{
var result = new List<(string name, string value)>();
foreach (var header in headers)
{
if (header.name == Header.AUTHORIZATION)
{
result.Add((Header.AUTHORIZATION, $"Bearer {oauth2Token}"));
}
else
{
result.Add(header);
}
}
return result.ToArray();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -15,10 +15,10 @@ using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server.Requests
{
internal class ConfirmLockScreenRequest : BaseRequest
internal class ConfirmLockScreenRequest : Request
{
internal ConfirmLockScreenRequest(
ApiVersion1 api,
Api api,
HttpClient httpClient,
ILogger logger,
Parser parser,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -13,10 +13,10 @@ using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server.Requests
{
internal class DisconnectionRequest : BaseRequest
internal class DisconnectionRequest : Request
{
internal DisconnectionRequest(
ApiVersion1 api,
Api api,
HttpClient httpClient,
ILogger logger,
Parser parser,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -14,10 +14,10 @@ using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server.Requests
{
internal class ExamConfigurationRequest : BaseRequest
internal class ExamConfigurationRequest : Request
{
internal ExamConfigurationRequest(
ApiVersion1 api,
Api api,
HttpClient httpClient,
ILogger logger,
Parser parser,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -15,10 +15,10 @@ using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server.Requests
{
internal class LockScreenRequest : BaseRequest
internal class LockScreenRequest : Request
{
internal LockScreenRequest(
ApiVersion1 api,
Api api,
HttpClient httpClient,
ILogger logger,
Parser parser,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -14,10 +14,10 @@ using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server.Requests
{
internal class LogRequest : BaseRequest
internal class LogRequest : Request
{
internal LogRequest(
ApiVersion1 api,
Api api,
HttpClient httpClient,
ILogger logger,
Parser parser,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -15,10 +15,10 @@ using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server.Requests
{
internal class LowerHandRequest : BaseRequest
internal class LowerHandRequest : Request
{
internal LowerHandRequest(
ApiVersion1 api,
Api api,
HttpClient httpClient,
ILogger logger,
Parser parser,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -17,10 +17,10 @@ using SafeExamBrowser.SystemComponents.Contracts.Network;
namespace SafeExamBrowser.Server.Requests
{
internal class NetworkAdapterRequest : BaseRequest
internal class NetworkAdapterRequest : Request
{
internal NetworkAdapterRequest(
ApiVersion1 api,
Api api,
HttpClient httpClient,
ILogger logger,
Parser parser,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -13,10 +13,10 @@ using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server.Requests
{
internal class OAuth2TokenRequest : BaseRequest
internal class OAuth2TokenRequest : Request
{
internal OAuth2TokenRequest(
ApiVersion1 api,
Api api,
HttpClient httpClient,
ILogger logger,
Parser parser,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -14,10 +14,10 @@ using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server.Requests
{
internal class PingRequest : BaseRequest
internal class PingRequest : Request
{
internal PingRequest(
ApiVersion1 api,
Api api,
HttpClient httpClient,
ILogger logger,
Parser parser,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -17,10 +17,10 @@ using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
namespace SafeExamBrowser.Server.Requests
{
internal class PowerSupplyRequest : BaseRequest
internal class PowerSupplyRequest : Request
{
internal PowerSupplyRequest(
ApiVersion1 api,
Api api,
HttpClient httpClient,
ILogger logger,
Parser parser,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -15,10 +15,10 @@ using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server.Requests
{
internal class RaiseHandRequest : BaseRequest
internal class RaiseHandRequest : Request
{
internal RaiseHandRequest(
ApiVersion1 api,
Api api,
HttpClient httpClient,
ILogger logger,
Parser parser,

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -14,16 +14,17 @@ using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server.Requests
{
internal class SelectExamRequest : BaseRequest
internal class SelectExamRequest : Request
{
internal SelectExamRequest(ApiVersion1 api, HttpClient httpClient, ILogger logger, Parser parser, ServerSettings settings) : base(api, httpClient, logger, parser, settings)
internal SelectExamRequest(Api api, HttpClient httpClient, ILogger logger, Parser parser, ServerSettings settings) : base(api, httpClient, logger, parser, settings)
{
}
internal bool TryExecute(Exam exam, out string message, out string appSignatureKeySalt, out string browserExamKey)
{
var content = $"examId={exam.Id}";
var success = TryExecute(HttpMethod.Put, api.HandshakeEndpoint, out var response, content, ContentType.URL_ENCODED, Authorization, Token);
var method = new HttpMethod("PATCH");
var success = TryExecute(method, api.HandshakeEndpoint, out var response, content, ContentType.URL_ENCODED, Authorization, Token);
appSignatureKeySalt = default;
browserExamKey = default;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -13,10 +13,10 @@ using SafeExamBrowser.Settings.Server;
namespace SafeExamBrowser.Server.Requests
{
internal class UserIdentifierRequest : BaseRequest
internal class UserIdentifierRequest : Request
{
internal UserIdentifierRequest(
ApiVersion1 api,
Api api,
HttpClient httpClient,
ILogger logger,
Parser parser,

View File

@@ -59,18 +59,18 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup>
<Compile Include="Data\ApiVersion1.cs" />
<Compile Include="Data\Api.cs" />
<Compile Include="Data\Attributes.cs" />
<Compile Include="Data\AttributeType.cs" />
<Compile Include="Data\Instructions.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="Requests\Extensions.cs" />
<Compile Include="FileSystem.cs" />
<Compile Include="Parser.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Requests\ApiRequest.cs" />
<Compile Include="Requests\AppSignatureKeyRequest.cs" />
<Compile Include="Requests\FinishHandshakeRequest.cs" />
<Compile Include="Requests\AvailableExamsRequest.cs" />
<Compile Include="Requests\BaseRequest.cs" />
<Compile Include="Requests\Request.cs" />
<Compile Include="Requests\ConfirmLockScreenRequest.cs" />
<Compile Include="Requests\ContentType.cs" />
<Compile Include="Requests\DisconnectionRequest.cs" />
@@ -86,6 +86,7 @@
<Compile Include="Requests\RaiseHandRequest.cs" />
<Compile Include="Requests\SelectExamRequest.cs" />
<Compile Include="Requests\UserIdentifierRequest.cs" />
<Compile Include="Sanitizer.cs" />
<Compile Include="ServerProxy.cs" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* 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
@@ -43,11 +43,12 @@ namespace SafeExamBrowser.Server
private readonly Parser parser;
private readonly Timer pingTimer;
private readonly IPowerSupply powerSupply;
private readonly Sanitizer sanitizer;
private readonly ISystemInfo systemInfo;
private readonly IUserInfo userInfo;
private readonly INetworkAdapter networkAdapter;
private ApiVersion1 api;
private Api api;
private int currentHandId;
private int currentLockScreenId;
private string examId;
@@ -75,7 +76,7 @@ namespace SafeExamBrowser.Server
IPowerSupply powerSupply = default,
INetworkAdapter networkAdapter = default)
{
this.api = new ApiVersion1();
this.api = new Api();
this.appConfig = appConfig;
this.keyGenerator = keyGenerator;
this.fileSystem = new FileSystem(appConfig, logger);
@@ -87,6 +88,7 @@ namespace SafeExamBrowser.Server
this.parser = new Parser(logger);
this.pingTimer = new Timer();
this.powerSupply = powerSupply;
this.sanitizer = new Sanitizer();
this.systemInfo = systemInfo;
this.userInfo = userInfo;
}
@@ -110,7 +112,7 @@ namespace SafeExamBrowser.Server
public ServerResponse Connect()
{
var request = new ApiRequest(api, httpClient, logger, parser, settings);
var request = new ApiRequest(api, httpClient, logger, parser, sanitizer, settings);
var success = request.TryExecute(out api, out var message);
if (success)
@@ -194,8 +196,8 @@ namespace SafeExamBrowser.Server
return new ConnectionInfo
{
Api = JsonConvert.SerializeObject(api),
ConnectionToken = BaseRequest.ConnectionToken,
Oauth2Token = BaseRequest.Oauth2Token
ConnectionToken = Request.ConnectionToken,
Oauth2Token = Request.Oauth2Token
};
}
@@ -204,7 +206,7 @@ namespace SafeExamBrowser.Server
this.settings = settings;
httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(settings.ServerUrl);
httpClient.BaseAddress = sanitizer.Sanitize(settings.ServerUrl);
if (settings.RequestTimeout > 0)
{
@@ -214,11 +216,11 @@ namespace SafeExamBrowser.Server
public void Initialize(string api, string connectionToken, string examId, string oauth2Token, ServerSettings settings)
{
this.api = JsonConvert.DeserializeObject<ApiVersion1>(api);
this.api = JsonConvert.DeserializeObject<Api>(api);
this.examId = examId;
BaseRequest.ConnectionToken = connectionToken;
BaseRequest.Oauth2Token = oauth2Token;
Request.ConnectionToken = connectionToken;
Request.Oauth2Token = oauth2Token;
Initialize(settings);
}
@@ -284,30 +286,22 @@ namespace SafeExamBrowser.Server
var request = new SelectExamRequest(api, httpClient, logger, parser, settings);
var success = request.TryExecute(exam, out var message, out var appSignatureKeySalt, out var browserExamKey);
if (browserExamKey != default)
{
logger.Info("Custom browser exam key detected.");
}
if (success)
{
logger.Info("Successfully sent selected exam.");
success = TryFinishHandshake(out message, appSignatureKeySalt);
}
else
{
logger.Error("Failed to send selected exam!");
}
if (success && appSignatureKeySalt != default)
{
logger.Info("App signature key salt detected, performing key exchange...");
success = TrySendAppSignatureKey(appSignatureKeySalt, out message);
}
else
{
logger.Info("No app signature key salt detected, skipping key exchange.");
}
if (browserExamKey != default)
{
logger.Info("Custom browser exam key detected.");
}
return new ServerResponse<string>(success, browserExamKey, message);
}
@@ -515,19 +509,30 @@ namespace SafeExamBrowser.Server
}
}
private bool TrySendAppSignatureKey(string salt, out string message)
private bool TryFinishHandshake(out string message, string appSignatureKeySalt = default)
{
var appSignatureKey = keyGenerator.CalculateAppSignatureKey(BaseRequest.ConnectionToken, salt);
var request = new AppSignatureKeyRequest(api, httpClient, logger, parser, settings);
var success = request.TryExecute(appSignatureKey, out message);
var appSignatureKey = default(string);
if (success)
if (appSignatureKeySalt != default)
{
logger.Info("Successfully sent app signature key.");
logger.Info("App signature key salt available, performing key exchange...");
appSignatureKey = keyGenerator.CalculateAppSignatureKey(Request.ConnectionToken, appSignatureKeySalt);
}
else
{
logger.Error("Failed to send app signature key!");
logger.Info("App signature key salt not available, not performing key exchange.");
}
var request = new FinishHandshakeRequest(api, httpClient, logger, parser, settings);
var success = request.TryExecute(out message, appSignatureKey);
if (success)
{
logger.Info("Successfully finished handshake.");
}
else
{
logger.Error("Failed to finish handshake!");
}
return success;