2020-07-13 22:57:19 +02:00
|
|
|
|
/*
|
2024-03-05 18:37:42 +01:00
|
|
|
|
* Copyright (c) 2024 ETH Zürich, IT Services
|
2020-07-13 22:57:19 +02:00
|
|
|
|
*
|
|
|
|
|
* 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;
|
2020-11-30 18:30:29 +01:00
|
|
|
|
using System.Linq;
|
2020-07-13 22:57:19 +02:00
|
|
|
|
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;
|
2020-07-28 19:56:25 +02:00
|
|
|
|
using SafeExamBrowser.Server.Contracts.Data;
|
2020-07-13 22:57:19 +02:00
|
|
|
|
using SafeExamBrowser.Settings;
|
2020-07-27 15:58:30 +02:00
|
|
|
|
using SafeExamBrowser.SystemComponents.Contracts;
|
2022-03-16 11:45:06 +01:00
|
|
|
|
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
2020-07-13 22:57:19 +02:00
|
|
|
|
|
|
|
|
|
namespace SafeExamBrowser.Runtime.Operations
|
|
|
|
|
{
|
|
|
|
|
internal class ServerOperation : ConfigurationBaseOperation
|
|
|
|
|
{
|
2020-07-27 15:58:30 +02:00
|
|
|
|
private readonly IFileSystem fileSystem;
|
2020-07-13 22:57:19 +02:00
|
|
|
|
private readonly ILogger logger;
|
|
|
|
|
private readonly IServerProxy server;
|
|
|
|
|
|
|
|
|
|
public override event ActionRequiredEventHandler ActionRequired;
|
|
|
|
|
public override event StatusChangedEventHandler StatusChanged;
|
|
|
|
|
|
|
|
|
|
public ServerOperation(
|
|
|
|
|
string[] commandLineArgs,
|
|
|
|
|
IConfigurationRepository configuration,
|
2020-07-27 15:58:30 +02:00
|
|
|
|
IFileSystem fileSystem,
|
2020-07-13 22:57:19 +02:00
|
|
|
|
ILogger logger,
|
|
|
|
|
SessionContext context,
|
|
|
|
|
IServerProxy server) : base(commandLineArgs, configuration, context)
|
|
|
|
|
{
|
2020-07-27 15:58:30 +02:00
|
|
|
|
this.fileSystem = fileSystem;
|
2020-07-13 22:57:19 +02:00
|
|
|
|
this.logger = logger;
|
|
|
|
|
this.server = server;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override OperationResult Perform()
|
|
|
|
|
{
|
|
|
|
|
var result = OperationResult.Success;
|
|
|
|
|
|
|
|
|
|
if (Context.Next.Settings.SessionMode == SessionMode.Server)
|
|
|
|
|
{
|
2023-03-08 22:24:29 +01:00
|
|
|
|
var browserExamKey = default(string);
|
2023-02-24 15:48:54 +01:00
|
|
|
|
var exam = default(Exam);
|
|
|
|
|
var exams = default(IEnumerable<Exam>);
|
|
|
|
|
var uri = default(Uri);
|
|
|
|
|
|
2020-07-13 22:57:19 +02:00
|
|
|
|
logger.Info("Initializing server...");
|
|
|
|
|
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServer);
|
|
|
|
|
|
2020-07-22 18:11:51 +02:00
|
|
|
|
server.Initialize(Context.Next.Settings.Server);
|
|
|
|
|
|
2020-07-28 19:56:25 +02:00
|
|
|
|
var (abort, fallback, success) = TryPerformWithFallback(() => server.Connect());
|
2020-07-13 22:57:19 +02:00
|
|
|
|
|
|
|
|
|
if (success)
|
|
|
|
|
{
|
2023-02-24 15:48:54 +01:00
|
|
|
|
(abort, fallback, success) = TryPerformWithFallback(() => server.GetAvailableExams(Context.Next.Settings.Server.ExamId), out exams);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (success)
|
|
|
|
|
{
|
|
|
|
|
success = TrySelectExam(exams, out exam);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (success)
|
|
|
|
|
{
|
|
|
|
|
(abort, fallback, success) = TryPerformWithFallback(() => server.GetConfigurationFor(exam), out uri);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (success)
|
|
|
|
|
{
|
|
|
|
|
result = TryLoadServerSettings(exam, uri);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (success && result == OperationResult.Success)
|
|
|
|
|
{
|
2023-03-08 22:24:29 +01:00
|
|
|
|
(abort, fallback, success) = TryPerformWithFallback(() => server.SendSelectedExam(exam), out browserExamKey);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (browserExamKey != default)
|
|
|
|
|
{
|
|
|
|
|
Context.Next.Settings.Browser.CustomBrowserExamKey = browserExamKey;
|
2020-07-13 22:57:19 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (abort)
|
|
|
|
|
{
|
|
|
|
|
result = OperationResult.Aborted;
|
2021-07-30 18:38:41 +02:00
|
|
|
|
logger.Info("The user aborted the server operation.");
|
2020-07-13 22:57:19 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fallback)
|
|
|
|
|
{
|
2020-07-29 23:39:05 +02:00
|
|
|
|
Context.Next.Settings.SessionMode = SessionMode.Normal;
|
2020-07-13 22:57:19 +02:00
|
|
|
|
result = OperationResult.Success;
|
2021-07-30 18:38:41 +02:00
|
|
|
|
logger.Info("The user chose to fallback and start a normal session.");
|
2020-07-13 22:57:19 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override OperationResult Repeat()
|
|
|
|
|
{
|
2023-02-24 15:48:54 +01:00
|
|
|
|
var result = OperationResult.Success;
|
2022-03-21 09:34:57 +01:00
|
|
|
|
|
2023-02-24 15:48:54 +01:00
|
|
|
|
if (Context.Current.Settings.SessionMode == SessionMode.Server && Context.Next.Settings.SessionMode == SessionMode.Server)
|
|
|
|
|
{
|
|
|
|
|
result = AbortServerReconfiguration();
|
|
|
|
|
}
|
|
|
|
|
else if (Context.Current.Settings.SessionMode == SessionMode.Server)
|
|
|
|
|
{
|
|
|
|
|
result = Revert();
|
2020-07-13 22:57:19 +02:00
|
|
|
|
}
|
2020-12-03 18:19:18 +01:00
|
|
|
|
else if (Context.Next.Settings.SessionMode == SessionMode.Server)
|
|
|
|
|
{
|
2023-02-24 15:48:54 +01:00
|
|
|
|
result = Perform();
|
2020-12-03 18:19:18 +01:00
|
|
|
|
}
|
2020-07-13 22:57:19 +02:00
|
|
|
|
|
2023-02-24 15:48:54 +01:00
|
|
|
|
return result;
|
2020-07-13 22:57:19 +02:00
|
|
|
|
}
|
2022-03-21 09:34:57 +01:00
|
|
|
|
|
2020-07-13 22:57:19 +02:00
|
|
|
|
public override OperationResult Revert()
|
|
|
|
|
{
|
2020-07-28 19:56:25 +02:00
|
|
|
|
var result = OperationResult.Success;
|
2020-07-13 22:57:19 +02:00
|
|
|
|
|
2022-08-24 17:56:09 +02:00
|
|
|
|
if (Context.Current?.Settings.SessionMode == SessionMode.Server || Context.Next?.Settings.SessionMode == SessionMode.Server)
|
2020-07-13 22:57:19 +02:00
|
|
|
|
{
|
|
|
|
|
logger.Info("Finalizing server...");
|
|
|
|
|
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServer);
|
|
|
|
|
|
|
|
|
|
var disconnect = server.Disconnect();
|
|
|
|
|
|
|
|
|
|
if (disconnect.Success)
|
|
|
|
|
{
|
|
|
|
|
result = OperationResult.Success;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
result = OperationResult.Failed;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void InvokeActionRequired(ActionRequiredEventArgs args)
|
|
|
|
|
{
|
|
|
|
|
ActionRequired?.Invoke(args);
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-03 12:15:54 +01:00
|
|
|
|
private OperationResult TryLoadServerSettings(Exam exam, Uri uri)
|
|
|
|
|
{
|
|
|
|
|
var info = server.GetConnectionInfo();
|
|
|
|
|
var result = OperationResult.Failed;
|
|
|
|
|
var status = TryLoadSettings(uri, UriSource.Server, out _, out var settings);
|
|
|
|
|
|
|
|
|
|
fileSystem.Delete(uri.LocalPath);
|
|
|
|
|
|
|
|
|
|
if (status == LoadStatus.Success)
|
|
|
|
|
{
|
2024-07-01 15:25:08 +02:00
|
|
|
|
var browserSettings = Context.Next.Settings.Browser;
|
2022-02-03 12:15:54 +01:00
|
|
|
|
var serverSettings = Context.Next.Settings.Server;
|
|
|
|
|
|
|
|
|
|
Context.Next.AppConfig.ServerApi = info.Api;
|
|
|
|
|
Context.Next.AppConfig.ServerConnectionToken = info.ConnectionToken;
|
|
|
|
|
Context.Next.AppConfig.ServerExamId = exam.Id;
|
|
|
|
|
Context.Next.AppConfig.ServerOauth2Token = info.Oauth2Token;
|
|
|
|
|
|
|
|
|
|
Context.Next.Settings = settings;
|
|
|
|
|
Context.Next.Settings.Browser.StartUrl = exam.Url;
|
2024-07-01 15:25:08 +02:00
|
|
|
|
Context.Next.Settings.Browser.StartUrlQuery = browserSettings.StartUrlQuery;
|
2022-02-03 12:15:54 +01:00
|
|
|
|
Context.Next.Settings.Server = serverSettings;
|
|
|
|
|
Context.Next.Settings.SessionMode = SessionMode.Server;
|
|
|
|
|
|
|
|
|
|
result = OperationResult.Success;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-13 22:57:19 +02:00
|
|
|
|
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;
|
|
|
|
|
|
2022-02-03 12:15:54 +01:00
|
|
|
|
value = default;
|
2020-07-13 22:57:19 +02:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2020-07-24 18:22:22 +02:00
|
|
|
|
var args = new ServerFailureEventArgs(message, Context.Next.Settings.Server.PerformFallback);
|
2020-07-13 22:57:19 +02:00
|
|
|
|
|
|
|
|
|
ActionRequired?.Invoke(args);
|
|
|
|
|
|
|
|
|
|
abort = args.Abort;
|
|
|
|
|
fallback = args.Fallback;
|
|
|
|
|
|
2023-09-05 17:47:05 +02:00
|
|
|
|
if (args.Retry)
|
|
|
|
|
{
|
|
|
|
|
logger.Debug("The user chose to retry the current server request.");
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-13 22:57:19 +02:00
|
|
|
|
return args.Retry;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-22 18:11:51 +02:00
|
|
|
|
private bool TrySelectExam(IEnumerable<Exam> exams, out Exam exam)
|
2020-07-13 22:57:19 +02:00
|
|
|
|
{
|
2022-02-03 12:15:54 +01:00
|
|
|
|
var success = true;
|
2020-07-13 22:57:19 +02:00
|
|
|
|
|
2022-02-03 12:15:54 +01:00
|
|
|
|
if (string.IsNullOrWhiteSpace(Context.Next.Settings.Server.ExamId))
|
|
|
|
|
{
|
|
|
|
|
var args = new ExamSelectionEventArgs(exams);
|
|
|
|
|
|
|
|
|
|
ActionRequired?.Invoke(args);
|
|
|
|
|
|
|
|
|
|
exam = args.SelectedExam;
|
|
|
|
|
success = args.Success;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
exam = exams.First();
|
|
|
|
|
logger.Info("Automatically selected exam as defined in configuration.");
|
|
|
|
|
}
|
2020-07-13 22:57:19 +02:00
|
|
|
|
|
2022-02-03 12:15:54 +01:00
|
|
|
|
return success;
|
2020-07-13 22:57:19 +02:00
|
|
|
|
}
|
2022-03-16 11:45:06 +01:00
|
|
|
|
|
2023-02-24 15:48:54 +01:00
|
|
|
|
private OperationResult AbortServerReconfiguration()
|
2022-03-16 11:45:06 +01:00
|
|
|
|
{
|
2022-03-21 09:34:57 +01:00
|
|
|
|
var args = new MessageEventArgs
|
|
|
|
|
{
|
2022-03-16 11:45:06 +01:00
|
|
|
|
Action = MessageBoxAction.Ok,
|
|
|
|
|
Icon = MessageBoxIcon.Warning,
|
|
|
|
|
Message = TextKey.MessageBox_ServerReconfigurationWarning,
|
|
|
|
|
Title = TextKey.MessageBox_ServerReconfigurationWarningTitle
|
|
|
|
|
};
|
|
|
|
|
|
2023-02-24 15:48:54 +01:00
|
|
|
|
logger.Warn("Server reconfiguration is currently not supported, aborting...");
|
2022-03-16 11:45:06 +01:00
|
|
|
|
ActionRequired?.Invoke(args);
|
2023-02-24 15:48:54 +01:00
|
|
|
|
|
|
|
|
|
return OperationResult.Aborted;
|
2022-03-16 11:45:06 +01:00
|
|
|
|
}
|
2020-07-13 22:57:19 +02:00
|
|
|
|
}
|
|
|
|
|
}
|