diff --git a/SafeExamBrowser.Browser.UnitTests/Handlers/RequestHandlerTests.cs b/SafeExamBrowser.Browser.UnitTests/Handlers/RequestHandlerTests.cs index f09bdc81..ee63cdbc 100644 --- a/SafeExamBrowser.Browser.UnitTests/Handlers/RequestHandlerTests.cs +++ b/SafeExamBrowser.Browser.UnitTests/Handlers/RequestHandlerTests.cs @@ -18,6 +18,7 @@ using SafeExamBrowser.Settings.Browser.Filter; using SafeExamBrowser.Settings.Browser.Proxy; using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings; using Request = SafeExamBrowser.Browser.Contracts.Filters.Request; +using ResourceHandler = SafeExamBrowser.Browser.Handlers.ResourceHandler; namespace SafeExamBrowser.Browser.UnitTests.Handlers { @@ -28,6 +29,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers private Mock filter; private Mock logger; private BrowserSettings settings; + private ResourceHandler resourceHandler; private Mock text; private TestableRequestHandler sut; @@ -39,8 +41,9 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers logger = new Mock(); settings = new BrowserSettings(); text = new Mock(); + resourceHandler = new ResourceHandler(appConfig, settings, filter.Object, logger.Object, text.Object); - sut = new TestableRequestHandler(appConfig, filter.Object, logger.Object, settings, text.Object); + sut = new TestableRequestHandler(appConfig, filter.Object, logger.Object, settings, resourceHandler, text.Object); } [TestMethod] @@ -194,7 +197,13 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers private class TestableRequestHandler : RequestHandler { - internal TestableRequestHandler(AppConfig appConfig, IRequestFilter filter, ILogger logger, BrowserSettings settings, IText text) : base(appConfig, filter, logger, settings, text) + internal TestableRequestHandler( + AppConfig appConfig, + IRequestFilter filter, + ILogger logger, + BrowserSettings settings, + ResourceHandler resourceHandler, + IText text) : base(appConfig, filter, logger, settings, resourceHandler, text) { } diff --git a/SafeExamBrowser.Browser/BrowserApplication.cs b/SafeExamBrowser.Browser/BrowserApplication.cs index f686ed9f..c2d47a15 100644 --- a/SafeExamBrowser.Browser/BrowserApplication.cs +++ b/SafeExamBrowser.Browser/BrowserApplication.cs @@ -10,8 +10,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading.Tasks; -using System.Timers; using CefSharp; using CefSharp.WinForms; using SafeExamBrowser.Applications.Contracts; @@ -41,10 +39,8 @@ 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; } @@ -72,10 +68,8 @@ 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; } @@ -111,15 +105,12 @@ 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; @@ -155,6 +146,7 @@ namespace SafeExamBrowser.Browser instance.ConfigurationDownloadRequested += (fileName, args) => ConfigurationDownloadRequested?.Invoke(fileName, args); instance.PopupRequested += Instance_PopupRequested; + instance.SessionIdentifierDetected += (i) => SessionIdentifierDetected?.Invoke(i); instance.Terminated += Instance_Terminated; instance.TerminationRequested += () => TerminationRequested?.Invoke(); @@ -304,20 +296,6 @@ 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(); - timer.Elapsed -= Timer_Elapsed; - } - private string ToScheme(ProxyProtocol protocol) { switch (protocol) @@ -346,40 +324,5 @@ 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"); - var moodleSession = cookies.FirstOrDefault(c => c.Name == "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); - Task.Run(() => SessionIdentifierDetected?.Invoke(edxSession.Value)); - } - } - - if (moodleSession != default(Cookie) && !sessionCookies.Contains(moodleSession.Domain)) - { - sessionCookies.Add(moodleSession.Domain); - Task.Run(() => SessionIdentifierDetected?.Invoke(moodleSession.Value)); - } - } - catch (Exception e) - { - logger.Error("Failed to read cookies!", e); - } - - timer.Start(); - } } } diff --git a/SafeExamBrowser.Browser/BrowserApplicationInstance.cs b/SafeExamBrowser.Browser/BrowserApplicationInstance.cs index 4b2b5265..d90584b7 100644 --- a/SafeExamBrowser.Browser/BrowserApplicationInstance.cs +++ b/SafeExamBrowser.Browser/BrowserApplicationInstance.cs @@ -30,6 +30,7 @@ using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog; using SafeExamBrowser.UserInterface.Contracts.MessageBox; using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings; using Request = SafeExamBrowser.Browser.Contracts.Filters.Request; +using ResourceHandler = SafeExamBrowser.Browser.Handlers.ResourceHandler; using TitleChangedEventHandler = SafeExamBrowser.Applications.Contracts.Events.TitleChangedEventHandler; namespace SafeExamBrowser.Browser @@ -65,6 +66,7 @@ namespace SafeExamBrowser.Browser internal event DownloadRequestedEventHandler ConfigurationDownloadRequested; internal event PopupRequestedEventHandler PopupRequested; + internal event SessionIdentifierDetectedEventHandler SessionIdentifierDetected; internal event InstanceTerminatedEventHandler Terminated; internal event TerminationRequestedEventHandler TerminationRequested; @@ -124,7 +126,8 @@ namespace SafeExamBrowser.Browser var lifeSpanHandler = new LifeSpanHandler(); var requestFilter = new RequestFilter(); var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} #{Id}"); - var requestHandler = new RequestHandler(appConfig, requestFilter, requestLogger, settings, text); + var resourceHandler = new ResourceHandler(appConfig, settings, requestFilter, logger, text); + var requestHandler = new RequestHandler(appConfig, requestFilter, requestLogger, settings, resourceHandler, text); Icon = new BrowserIconResource(); @@ -138,6 +141,7 @@ namespace SafeExamBrowser.Browser keyboardHandler.ZoomOutRequested += ZoomOutRequested; keyboardHandler.ZoomResetRequested += ZoomResetRequested; lifeSpanHandler.PopupRequested += LifeSpanHandler_PopupRequested; + resourceHandler.SessionIdentifierDetected += (id) => SessionIdentifierDetected?.Invoke(id); requestHandler.QuitUrlVisited += RequestHandler_QuitUrlVisited; requestHandler.RequestBlocked += RequestHandler_RequestBlocked; diff --git a/SafeExamBrowser.Browser/Handlers/RequestHandler.cs b/SafeExamBrowser.Browser/Handlers/RequestHandler.cs index e2d616e0..1d27f92c 100644 --- a/SafeExamBrowser.Browser/Handlers/RequestHandler.cs +++ b/SafeExamBrowser.Browser/Handlers/RequestHandler.cs @@ -31,12 +31,18 @@ namespace SafeExamBrowser.Browser.Handlers internal event UrlEventHandler QuitUrlVisited; internal event UrlEventHandler RequestBlocked; - internal RequestHandler(AppConfig appConfig, IRequestFilter filter, ILogger logger, BrowserSettings settings, IText text) + internal RequestHandler( + AppConfig appConfig, + IRequestFilter filter, + ILogger logger, + BrowserSettings settings, + ResourceHandler resourceHandler, + IText text) { this.filter = filter; this.logger = logger; this.settings = settings; - this.resourceHandler = new ResourceHandler(appConfig, settings, filter, logger, text); + this.resourceHandler = resourceHandler; } protected override bool GetAuthCredentials(IWebBrowser webBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback) diff --git a/SafeExamBrowser.Browser/Handlers/ResourceHandler.cs b/SafeExamBrowser.Browser/Handlers/ResourceHandler.cs index 9019719e..a15bdd0c 100644 --- a/SafeExamBrowser.Browser/Handlers/ResourceHandler.cs +++ b/SafeExamBrowser.Browser/Handlers/ResourceHandler.cs @@ -8,10 +8,15 @@ using System; using System.Collections.Specialized; +using System.Linq; using System.Net.Mime; using System.Security.Cryptography; using System.Text; +using System.Threading.Tasks; using CefSharp; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using SafeExamBrowser.Browser.Contracts.Events; using SafeExamBrowser.Browser.Contracts.Filters; using SafeExamBrowser.Browser.Pages; using SafeExamBrowser.Configuration.Contracts; @@ -36,6 +41,8 @@ namespace SafeExamBrowser.Browser.Handlers private BrowserSettings settings; private IText text; + internal event SessionIdentifierDetectedEventHandler SessionIdentifierDetected; + internal ResourceHandler(AppConfig appConfig, BrowserSettings settings, IRequestFilter filter, ILogger logger, IText text) { this.appConfig = appConfig; @@ -70,6 +77,13 @@ namespace SafeExamBrowser.Browser.Handlers return base.OnBeforeResourceLoad(webBrowser, browser, frame, request, callback); } + protected override void OnResourceRedirect(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, ref string newUrl) + { + SearchSessionIdentifiers(response); + + base.OnResourceRedirect(chromiumWebBrowser, browser, frame, request, response, ref newUrl); + } + protected override bool OnResourceResponse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response) { if (RedirectToDisablePdfToolbar(request, response, out var url)) @@ -79,6 +93,8 @@ namespace SafeExamBrowser.Browser.Handlers return true; } + SearchSessionIdentifiers(response); + return base.OnResourceResponse(webBrowser, browser, frame, request, response); } @@ -206,5 +222,64 @@ namespace SafeExamBrowser.Browser.Handlers return contentHandler; } } + + private void SearchSessionIdentifiers(IResponse response) + { + SearchEdxIdentifier(response); + SearchMoodleIdentifier(response); + } + + private void SearchEdxIdentifier(IResponse response) + { + var cookies = response.Headers.GetValues("Set-Cookie"); + + if (cookies != default(string[])) + { + try + { + var userInfo = cookies.FirstOrDefault(c => c.Contains("edx-user-info")); + + if (userInfo != default(string)) + { + var start = userInfo.IndexOf("=") + 1; + var end = userInfo.IndexOf("; expires"); + var cookie = userInfo.Substring(start, end - start); + var sanitized = cookie.Replace("\\\"", "\"").Replace("\\054", ",").Trim('"'); + var json = JsonConvert.DeserializeObject(sanitized) as JObject; + var userName = json["username"].Value(); + + Task.Run(() => SessionIdentifierDetected?.Invoke(userName)); + } + } + catch (Exception e) + { + logger.Error("Failed to parse edX session identifier!", e); + } + } + } + + private void SearchMoodleIdentifier(IResponse response) + { + var locations = response.Headers.GetValues("Location"); + + if (locations != default(string[])) + { + try + { + var location = locations.FirstOrDefault(l => l.Contains("moodle/login/index.php?testsession")); + + if (location != default(string)) + { + var userId = location.Substring(location.IndexOf("=") + 1); + + Task.Run(() => SessionIdentifierDetected?.Invoke(userId)); + } + } + catch (Exception e) + { + logger.Error("Failed to parse Moodle session identifier!", e); + } + } + } } } diff --git a/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj b/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj index aba4d418..546bf5ff 100644 --- a/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj +++ b/SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj @@ -54,6 +54,9 @@ prompt + + ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll + ..\packages\Syroot.Windows.IO.KnownFolders.1.2.3\lib\netstandard2.0\Syroot.KnownFolders.dll diff --git a/SafeExamBrowser.Browser/packages.config b/SafeExamBrowser.Browser/packages.config index 4a81fc71..b9a583e6 100644 --- a/SafeExamBrowser.Browser/packages.config +++ b/SafeExamBrowser.Browser/packages.config @@ -4,6 +4,7 @@ + \ No newline at end of file diff --git a/SafeExamBrowser.Runtime/Operations/ServerOperation.cs b/SafeExamBrowser.Runtime/Operations/ServerOperation.cs index 2b03d6e8..dcf56c69 100644 --- a/SafeExamBrowser.Runtime/Operations/ServerOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ServerOperation.cs @@ -77,7 +77,6 @@ namespace SafeExamBrowser.Runtime.Operations if (status == LoadStatus.Success) { - // TODO: Why aren't the server settings and SEB mode correctly set in the exam configuration? var serverSettings = Context.Next.Settings.Server; Context.Next.AppConfig.ServerApi = info.Api; diff --git a/SafeExamBrowser.Server/ServerProxy.cs b/SafeExamBrowser.Server/ServerProxy.cs index 3ee59b5d..e6767540 100644 --- a/SafeExamBrowser.Server/ServerProxy.cs +++ b/SafeExamBrowser.Server/ServerProxy.cs @@ -256,20 +256,28 @@ namespace SafeExamBrowser.Server task = new Task(SendLog, cancellationTokenSource.Token); task.Start(); + logger.Info("Started sending log items."); + timer.AutoReset = false; timer.Elapsed += Timer_Elapsed; timer.Interval = 1000; timer.Start(); + + logger.Info("Starting sending pings."); } public void StopConnectivity() { logger.Unsubscribe(this); cancellationTokenSource.Cancel(); - task.Wait(); + task?.Wait(); + + logger.Info("Stopped sending log items."); timer.Stop(); timer.Elapsed -= Timer_Elapsed; + + logger.Info("Stopped sending pings."); } private void SendLog() @@ -278,8 +286,6 @@ namespace SafeExamBrowser.Server var contentType = "application/json;charset=UTF-8"; var token = ("SEBConnectionToken", connectionToken); - logger.Info("Starting to send log items..."); - while (!cancellationTokenSource.IsCancellationRequested) { try @@ -293,7 +299,6 @@ namespace SafeExamBrowser.Server ["text"] = message.Message }; var content = json.ToString(); - // TODO: Why can't we send multiple log messages in one request? var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, content, contentType, authorization, token); } } @@ -302,8 +307,6 @@ namespace SafeExamBrowser.Server logger.Error("Failed to send log!", e); } } - - logger.Info("Stopped sending log items."); } private void Timer_Elapsed(object sender, ElapsedEventArgs args)