diff --git a/SafeExamBrowser.Browser.Contracts/Events/SessionIdentifierDetectedEventHandler.cs b/SafeExamBrowser.Browser.Contracts/Events/SessionIdentifierDetectedEventHandler.cs new file mode 100644 index 00000000..f56674fb --- /dev/null +++ b/SafeExamBrowser.Browser.Contracts/Events/SessionIdentifierDetectedEventHandler.cs @@ -0,0 +1,15 @@ +/* + * 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.Browser.Contracts.Events +{ + /// + /// Event handler used to indicate that the browser has detected a session identifier of a LMS. + /// + public delegate void SessionIdentifierDetectedEventHandler(string identifier); +} diff --git a/SafeExamBrowser.Browser.Contracts/IBrowserApplication.cs b/SafeExamBrowser.Browser.Contracts/IBrowserApplication.cs index 6b8965e9..dbce4df4 100644 --- a/SafeExamBrowser.Browser.Contracts/IBrowserApplication.cs +++ b/SafeExamBrowser.Browser.Contracts/IBrowserApplication.cs @@ -21,6 +21,11 @@ namespace SafeExamBrowser.Browser.Contracts /// event DownloadRequestedEventHandler ConfigurationDownloadRequested; + /// + /// Event fired when the browser application detects a session identifier of an LMS. + /// + event SessionIdentifierDetectedEventHandler SessionIdentifierDetected; + /// /// Event fired when the browser application detects a request to terminate SEB. /// diff --git a/SafeExamBrowser.Browser.Contracts/SafeExamBrowser.Browser.Contracts.csproj b/SafeExamBrowser.Browser.Contracts/SafeExamBrowser.Browser.Contracts.csproj index 9702d106..542024a9 100644 --- a/SafeExamBrowser.Browser.Contracts/SafeExamBrowser.Browser.Contracts.csproj +++ b/SafeExamBrowser.Browser.Contracts/SafeExamBrowser.Browser.Contracts.csproj @@ -57,6 +57,7 @@ + diff --git a/SafeExamBrowser.Browser/BrowserApplication.cs b/SafeExamBrowser.Browser/BrowserApplication.cs index c3b7869e..cc08a4c8 100644 --- a/SafeExamBrowser.Browser/BrowserApplication.cs +++ b/SafeExamBrowser.Browser/BrowserApplication.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Timers; using CefSharp; using CefSharp.WinForms; using SafeExamBrowser.Applications.Contracts; @@ -39,8 +40,10 @@ namespace SafeExamBrowser.Browser private IFileSystemDialog fileSystemDialog; private IMessageBox messageBox; private IModuleLogger logger; + private List sessionCookies; private BrowserSettings settings; private IText text; + private Timer timer; private IUserInterfaceFactory uiFactory; public bool AutoStart { get; private set; } @@ -50,8 +53,9 @@ namespace SafeExamBrowser.Browser public string Tooltip { get; private set; } public event DownloadRequestedEventHandler ConfigurationDownloadRequested; - public event WindowsChangedEventHandler WindowsChanged; + public event SessionIdentifierDetectedEventHandler SessionIdentifierDetected; public event TerminationRequestedEventHandler TerminationRequested; + public event WindowsChangedEventHandler WindowsChanged; public BrowserApplication( AppConfig appConfig, @@ -67,8 +71,10 @@ namespace SafeExamBrowser.Browser this.instances = new List(); this.logger = logger; this.messageBox = messageBox; + this.sessionCookies = new List(); this.settings = settings; this.text = text; + this.timer = new Timer(); this.uiFactory = uiFactory; } @@ -104,12 +110,15 @@ namespace SafeExamBrowser.Browser public void Start() { CreateNewInstance(); + StartMonitoringCookies(); } public void Terminate() { logger.Info("Initiating termination..."); + StopMonitoringCookies(); + foreach (var instance in instances) { instance.Terminated -= Instance_Terminated; @@ -294,6 +303,19 @@ namespace SafeExamBrowser.Browser return userAgent; } + private void StartMonitoringCookies() + { + timer.AutoReset = false; + timer.Interval = 1000; + timer.Elapsed += Timer_Elapsed; + timer.Start(); + } + + private void StopMonitoringCookies() + { + timer.Stop(); + } + private string ToScheme(ProxyProtocol protocol) { switch (protocol) @@ -322,5 +344,35 @@ namespace SafeExamBrowser.Browser instances.Remove(instances.First(i => i.Id == id)); WindowsChanged?.Invoke(); } + + private void Timer_Elapsed(object sender, ElapsedEventArgs args) + { + try + { + var manager = Cef.GetGlobalCookieManager(); + var task = manager.VisitAllCookiesAsync(); + var cookies = task.GetAwaiter().GetResult(); + var edxLogin = cookies.FirstOrDefault(c => c.Name == "edxloggedin"); + + // TODO: MoodleSession + + if (edxLogin != default(Cookie)) + { + var edxSession = cookies.FirstOrDefault(c => c.Domain == edxLogin.Domain && c.Name == "sessionid"); + + if (edxSession != default(Cookie) && !sessionCookies.Contains(edxSession.Domain)) + { + sessionCookies.Add(edxSession.Domain); + SessionIdentifierDetected?.Invoke(edxSession.Value); + } + } + } + catch (Exception e) + { + logger.Error("Failed to read cookies!", e); + } + + timer.Start(); + } } } diff --git a/SafeExamBrowser.Client/ClientContext.cs b/SafeExamBrowser.Client/ClientContext.cs index 901bf1e7..c38865d6 100644 --- a/SafeExamBrowser.Client/ClientContext.cs +++ b/SafeExamBrowser.Client/ClientContext.cs @@ -49,7 +49,7 @@ namespace SafeExamBrowser.Client internal IClientHost ClientHost { get; set; } /// - /// The server proxy, if the current session mode is . + /// The server proxy (if the current session mode is ). /// internal IServerProxy Server { get; set; } diff --git a/SafeExamBrowser.Client/ClientController.cs b/SafeExamBrowser.Client/ClientController.cs index 86ca9047..70777f35 100644 --- a/SafeExamBrowser.Client/ClientController.cs +++ b/SafeExamBrowser.Client/ClientController.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; using SafeExamBrowser.Applications.Contracts; using SafeExamBrowser.Browser.Contracts; using SafeExamBrowser.Browser.Contracts.Events; @@ -183,6 +184,7 @@ namespace SafeExamBrowser.Client applicationMonitor.ExplorerStarted += ApplicationMonitor_ExplorerStarted; applicationMonitor.TerminationFailed += ApplicationMonitor_TerminationFailed; Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested; + Browser.SessionIdentifierDetected += Browser_SessionIdentifierDetected; Browser.TerminationRequested += Browser_TerminationRequested; ClientHost.MessageBoxRequested += ClientHost_MessageBoxRequested; ClientHost.PasswordRequested += ClientHost_PasswordRequested; @@ -218,6 +220,8 @@ namespace SafeExamBrowser.Client if (Browser != null) { Browser.ConfigurationDownloadRequested -= Browser_ConfigurationDownloadRequested; + Browser.SessionIdentifierDetected -= Browser_SessionIdentifierDetected; + Browser.TerminationRequested -= Browser_TerminationRequested; } if (ClientHost != null) @@ -341,6 +345,24 @@ namespace SafeExamBrowser.Client } } + private void Browser_SessionIdentifierDetected(string identifier) + { + if (Settings.SessionMode == SessionMode.Server) + { + var response = Server.SendSessionIdentifier(identifier); + + while (!response.Success) + { + logger.Error($"Failed to communicate session identifier with server! {response.Message}"); + // TODO: Check that is running in separat thread (not UI thread!!) or use different mechanism to wait! + Thread.Sleep(Settings.Server.RequestAttemptInterval); + response = Server.SendSessionIdentifier(identifier); + } + + Server.StartConnectivity(); + } + } + private void Browser_TerminationRequested() { logger.Info("Attempting to shutdown as requested by the browser..."); diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs index 34936310..4b0f0e0f 100644 --- a/SafeExamBrowser.Client/CompositionRoot.cs +++ b/SafeExamBrowser.Client/CompositionRoot.cs @@ -244,6 +244,8 @@ namespace SafeExamBrowser.Client var server = new ServerProxy(context.AppConfig, logger); var operation = new ServerOperation(actionCenter, context, logger, server, taskbar); + context.Server = server; + return operation; } diff --git a/SafeExamBrowser.Client/Operations/ServerOperation.cs b/SafeExamBrowser.Client/Operations/ServerOperation.cs index 189930d3..dd232f71 100644 --- a/SafeExamBrowser.Client/Operations/ServerOperation.cs +++ b/SafeExamBrowser.Client/Operations/ServerOperation.cs @@ -48,7 +48,12 @@ namespace SafeExamBrowser.Client.Operations logger.Info("Initializing server..."); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServer); - server.Initialize(Context.AppConfig.ServerApi, Context.AppConfig.ServerConnectionToken, Context.AppConfig.ServerOauth2Token, Context.Settings.Server); + server.Initialize( + Context.AppConfig.ServerApi, + Context.AppConfig.ServerConnectionToken, + Context.AppConfig.ServerExamId, + Context.AppConfig.ServerOauth2Token, + Context.Settings.Server); // TODO: Add action center and taskbar notifications } @@ -66,6 +71,7 @@ namespace SafeExamBrowser.Client.Operations StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServer); // TODO: Stop sending pings and logs (or in controller?) + server.StopConnectivity(); // TODO: Stop action center and taskbar notifications } diff --git a/SafeExamBrowser.Configuration.Contracts/AppConfig.cs b/SafeExamBrowser.Configuration.Contracts/AppConfig.cs index 245f4608..e5479d02 100644 --- a/SafeExamBrowser.Configuration.Contracts/AppConfig.cs +++ b/SafeExamBrowser.Configuration.Contracts/AppConfig.cs @@ -161,6 +161,11 @@ namespace SafeExamBrowser.Configuration.Contracts /// public string ServerConnectionToken { get; set; } + /// + /// The identifier of the selected server exam. + /// + public string ServerExamId { get; set; } + /// /// The OAuth2 token for a server. /// diff --git a/SafeExamBrowser.Runtime/Operations/ServerOperation.cs b/SafeExamBrowser.Runtime/Operations/ServerOperation.cs index cdac4bda..dcf56c69 100644 --- a/SafeExamBrowser.Runtime/Operations/ServerOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ServerOperation.cs @@ -77,11 +77,18 @@ namespace SafeExamBrowser.Runtime.Operations if (status == LoadStatus.Success) { + 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; + Context.Next.Settings.Server = serverSettings; + Context.Next.Settings.SessionMode = SessionMode.Server; + result = OperationResult.Success; } else @@ -105,6 +112,7 @@ namespace SafeExamBrowser.Runtime.Operations if (fallback) { + Context.Next.Settings.SessionMode = SessionMode.Normal; result = OperationResult.Success; } } diff --git a/SafeExamBrowser.Server.Contracts/IServerProxy.cs b/SafeExamBrowser.Server.Contracts/IServerProxy.cs index eb779e1f..8fc5b4bd 100644 --- a/SafeExamBrowser.Server.Contracts/IServerProxy.cs +++ b/SafeExamBrowser.Server.Contracts/IServerProxy.cs @@ -51,11 +51,21 @@ namespace SafeExamBrowser.Server.Contracts /// /// Initializes the configuration and server settings to be used for communication. /// - void Initialize(string api, string connectionToken, string oauth2Token, ServerSettings settings); + void Initialize(string api, string connectionToken, string examId, string oauth2Token, ServerSettings settings); /// /// TODO /// - ServerResponse SendSessionInfo(string sessionId); + ServerResponse SendSessionIdentifier(string identifier); + + /// + /// TODO + /// + void StartConnectivity(); + + /// + /// TODO + /// + void StopConnectivity(); } } diff --git a/SafeExamBrowser.Server/ServerProxy.cs b/SafeExamBrowser.Server/ServerProxy.cs index 9e2c24a0..3418a8f8 100644 --- a/SafeExamBrowser.Server/ServerProxy.cs +++ b/SafeExamBrowser.Server/ServerProxy.cs @@ -29,6 +29,7 @@ namespace SafeExamBrowser.Server { private ApiVersion1 api; private string connectionToken; + private string examId; private HttpClient httpClient; private readonly AppConfig appConfig; private ILogger logger; @@ -173,20 +174,31 @@ namespace SafeExamBrowser.Server } } - public void Initialize(string api, string connectionToken, string oauth2Token, ServerSettings settings) + public void Initialize(string api, string connectionToken, string examId, string oauth2Token, ServerSettings settings) { this.api = JsonConvert.DeserializeObject(api); this.connectionToken = connectionToken; + this.examId = examId; this.oauth2Token = oauth2Token; Initialize(settings); } - public ServerResponse SendSessionInfo(string sessionId) + public ServerResponse SendSessionIdentifier(string identifier) { return new ServerResponse(false, "TODO!"); } + public void StartConnectivity() + { + // TODO: Start sending logs and pings + } + + public void StopConnectivity() + { + // TODO: Stop sending logs and pings + } + private bool TryParseApi(HttpContent content) { var success = false;