SEBWIN-405: Fixed and improved LMS session detection.
This commit is contained in:
parent
682c2a2ce5
commit
8d94750078
9 changed files with 113 additions and 70 deletions
|
@ -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<IRequestFilter> filter;
|
||||
private Mock<ILogger> logger;
|
||||
private BrowserSettings settings;
|
||||
private ResourceHandler resourceHandler;
|
||||
private Mock<IText> text;
|
||||
private TestableRequestHandler sut;
|
||||
|
||||
|
@ -39,8 +41,9 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
logger = new Mock<ILogger>();
|
||||
settings = new BrowserSettings();
|
||||
text = new Mock<IText>();
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -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<string> 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<BrowserApplicationInstance>();
|
||||
this.logger = logger;
|
||||
this.messageBox = messageBox;
|
||||
this.sessionCookies = new List<string>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<string>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,9 @@
|
|||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Syroot.KnownFolders, Version=1.2.3.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Syroot.Windows.IO.KnownFolders.1.2.3\lib\netstandard2.0\Syroot.KnownFolders.dll</HintPath>
|
||||
</Reference>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<package id="cef.redist.x86" version="81.3.10" targetFramework="net472" />
|
||||
<package id="CefSharp.Common" version="81.3.100" targetFramework="net472" />
|
||||
<package id="CefSharp.WinForms" version="81.3.100" targetFramework="net472" />
|
||||
<package id="Newtonsoft.Json" version="12.0.3" targetFramework="net472" />
|
||||
<package id="Syroot.Windows.IO.KnownFolders" version="1.2.3" targetFramework="net472" />
|
||||
<package id="System.Security.Principal.Windows" version="4.7.0" targetFramework="net472" />
|
||||
</packages>
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue