/* * 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.Security.AccessControl; using System.Threading; using SafeExamBrowser.Communication.Contracts.Hosts; using SafeExamBrowser.Communication.Contracts.Proxies; 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.Settings.Service; using SafeExamBrowser.SystemComponents.Contracts; using SafeExamBrowser.UserInterface.Contracts.MessageBox; namespace SafeExamBrowser.Runtime.Operations { internal class ServiceOperation : SessionOperation { private ILogger logger; private IRuntimeHost runtimeHost; private IServiceProxy service; private string serviceEventName; private Guid? sessionId; private int timeout_ms; private IUserInfo userInfo; public override event ActionRequiredEventHandler ActionRequired; public override event StatusChangedEventHandler StatusChanged; public ServiceOperation( ILogger logger, IRuntimeHost runtimeHost, IServiceProxy service, SessionContext sessionContext, int timeout_ms, IUserInfo userInfo) : base(sessionContext) { this.logger = logger; this.runtimeHost = runtimeHost; this.service = service; this.timeout_ms = timeout_ms; this.userInfo = userInfo; } public override OperationResult Perform() { logger.Info($"Initializing service..."); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession); var success = IgnoreService() || TryInitializeConnection(); if (success && service.IsConnected) { success = TryStartSession(); } return success ? OperationResult.Success : OperationResult.Failed; } public override OperationResult Repeat() { logger.Info($"Initializing service..."); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession); var success = true; if (service.IsConnected) { if (sessionId.HasValue) { success = TryStopSession(); } if (success && IgnoreService()) { success = TryTerminateConnection(); } } else { success = IgnoreService() || TryInitializeConnection(); } if (success && service.IsConnected) { success = TryStartSession(); } return success ? OperationResult.Success : OperationResult.Failed; } public override OperationResult Revert() { logger.Info("Finalizing service..."); StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServiceSession); var success = true; if (service.IsConnected) { if (sessionId.HasValue) { success = TryStopSession(true); } success &= TryTerminateConnection(); } return success ? OperationResult.Success : OperationResult.Failed; } private bool IgnoreService() { if (Context.Next.Settings.Service.IgnoreService) { logger.Info("The service will be ignored for the next session."); return true; } return false; } private bool TryInitializeConnection() { var mandatory = Context.Next.Settings.Service.Policy == ServicePolicy.Mandatory; var warn = Context.Next.Settings.Service.Policy == ServicePolicy.Warn; var connected = service.Connect(); var success = connected || !mandatory; if (success) { logger.Info($"The service is {(mandatory ? "mandatory" : "optional")} and {(connected ? "connected." : "not connected.")}"); if (!connected && warn) { ActionRequired?.Invoke(new MessageEventArgs { Icon = MessageBoxIcon.Warning, Message = TextKey.MessageBox_ServiceUnavailableWarning, Title = TextKey.MessageBox_ServiceUnavailableWarningTitle }); } } else { logger.Error("The service is mandatory but no connection could be established!"); ActionRequired?.Invoke(new MessageEventArgs { Icon = MessageBoxIcon.Error, Message = TextKey.MessageBox_ServiceUnavailableError, Title = TextKey.MessageBox_ServiceUnavailableErrorTitle }); } return success; } private bool TryTerminateConnection() { var disconnected = service.Disconnect(); if (disconnected) { logger.Info("Successfully disconnected from service."); } else { logger.Error("Failed to disconnect from service!"); } return disconnected; } private bool TryStartSession() { var configuration = new ServiceConfiguration { AppConfig = Context.Next.AppConfig, SessionId = Context.Next.SessionId, Settings = Context.Next.Settings, UserName = userInfo.GetUserName(), UserSid = userInfo.GetUserSid() }; var started = false; logger.Info("Starting new service session..."); var communication = service.StartSession(configuration); if (communication.Success) { started = TryWaitForServiceEvent(Context.Next.AppConfig.ServiceEventName); if (started) { sessionId = Context.Next.SessionId; serviceEventName = Context.Next.AppConfig.ServiceEventName; logger.Info("Successfully started new service session."); } else { logger.Error($"Failed to start new service session within {timeout_ms / 1000} seconds!"); } } else { logger.Error("Failed to communicate session start command to service!"); } return started; } private bool TryStopSession(bool isFinalSession = false) { var success = false; logger.Info("Stopping current service session..."); var communication = service.StopSession(sessionId.Value); if (communication.Success) { success = TryWaitForServiceEvent(serviceEventName); if (success) { sessionId = default(Guid?); serviceEventName = default(string); logger.Info("Successfully stopped service session."); } else { logger.Error($"Failed to stop service session within {timeout_ms / 1000} seconds!"); } } else { logger.Error("Failed to communicate session stop command to service!"); } if (success && isFinalSession) { communication = service.RunSystemConfigurationUpdate(); success = communication.Success; if (communication.Success) { logger.Info("Instructed service to perform system configuration update."); } else { logger.Error("Failed to communicate system configuration update command to service!"); } } return success; } private bool TryWaitForServiceEvent(string eventName) { var serviceEvent = default(EventWaitHandle); var startTime = DateTime.Now; do { if (EventWaitHandle.TryOpenExisting(eventName, EventWaitHandleRights.Synchronize, out serviceEvent)) { break; } } while (startTime.AddMilliseconds(timeout_ms) > DateTime.Now); if (serviceEvent != default(EventWaitHandle)) { using (serviceEvent) { return serviceEvent.WaitOne(timeout_ms); } } return false; } } }