SEBWIN-762: Added user identifier detection via Moodle plugin and overall renamed session to user identifier.
This commit is contained in:
parent
8c3d9a31d7
commit
751bfcb144
13 changed files with 173 additions and 125 deletions
|
@ -9,7 +9,7 @@
|
||||||
namespace SafeExamBrowser.Browser.Contracts.Events
|
namespace SafeExamBrowser.Browser.Contracts.Events
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event handler used to indicate that the browser has detected a session identifier of a LMS.
|
/// Event handler used to indicate that the browser has detected a user identifier of an LMS.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public delegate void SessionIdentifierDetectedEventHandler(string identifier);
|
public delegate void UserIdentifierDetectedEventHandler(string identifier);
|
||||||
}
|
}
|
|
@ -22,9 +22,9 @@ namespace SafeExamBrowser.Browser.Contracts
|
||||||
event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event fired when the browser application detects a session identifier of an LMS.
|
/// Event fired when the user tries to focus the taskbar.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
|
event LoseFocusRequestedEventHandler LoseFocusRequested;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event fired when the browser application detects a request to terminate SEB.
|
/// Event fired when the browser application detects a request to terminate SEB.
|
||||||
|
@ -32,9 +32,9 @@ namespace SafeExamBrowser.Browser.Contracts
|
||||||
event TerminationRequestedEventHandler TerminationRequested;
|
event TerminationRequestedEventHandler TerminationRequested;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event fired when the user tries to focus the taskbar.
|
/// Event fired when the browser application detects a user identifier of an LMS.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event LoseFocusRequestedEventHandler LoseFocusRequested;
|
event UserIdentifierDetectedEventHandler UserIdentifierDetected;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Transfers the focus to the browser application. If the parameter is <c>true</c>, the first focusable element in the browser window
|
/// Transfers the focus to the browser application. If the parameter is <c>true</c>, the first focusable element in the browser window
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
<Compile Include="Events\DownloadRequestedEventHandler.cs" />
|
<Compile Include="Events\DownloadRequestedEventHandler.cs" />
|
||||||
<Compile Include="Events\TabPressedEventHandler.cs" />
|
<Compile Include="Events\TabPressedEventHandler.cs" />
|
||||||
<Compile Include="Events\LoseFocusRequestedEventHandler.cs" />
|
<Compile Include="Events\LoseFocusRequestedEventHandler.cs" />
|
||||||
<Compile Include="Events\SessionIdentifierDetectedEventHandler.cs" />
|
<Compile Include="Events\UserIdentifierDetectedEventHandler.cs" />
|
||||||
<Compile Include="Events\TerminationRequestedEventHandler.cs" />
|
<Compile Include="Events\TerminationRequestedEventHandler.cs" />
|
||||||
<Compile Include="Filters\IRequestFilter.cs" />
|
<Compile Include="Filters\IRequestFilter.cs" />
|
||||||
<Compile Include="Filters\IRule.cs" />
|
<Compile Include="Filters\IRule.cs" />
|
||||||
|
|
|
@ -166,7 +166,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void MustLetOperatingSystemHandleUnknownProtocols()
|
public void MustLetOperatingSystemHandleUnknownProtocols()
|
||||||
{
|
{
|
||||||
Assert.IsTrue(sut.OnProtocolExecution(default(IWebBrowser), default(IBrowser), default(IFrame), default(IRequest)));
|
Assert.IsTrue(sut.OnProtocolExecution(default, default, default, default));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
@ -232,28 +232,28 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
||||||
var newUrl = default(string);
|
var newUrl = default(string);
|
||||||
var request = new Mock<IRequest>();
|
var request = new Mock<IRequest>();
|
||||||
var response = new Mock<IResponse>();
|
var response = new Mock<IResponse>();
|
||||||
var sessionId = default(string);
|
var userId = default(string);
|
||||||
|
|
||||||
headers.Add("X-LMS-USER-ID", "some-session-id-123");
|
headers.Add("X-LMS-USER-ID", "some-session-id-123");
|
||||||
request.SetupGet(r => r.Url).Returns("https://www.somelms.org");
|
request.SetupGet(r => r.Url).Returns("https://www.somelms.org");
|
||||||
response.SetupGet(r => r.Headers).Returns(headers);
|
response.SetupGet(r => r.Headers).Returns(headers);
|
||||||
sut.SessionIdentifierDetected += (id) =>
|
sut.UserIdentifierDetected += (id) =>
|
||||||
{
|
{
|
||||||
sessionId = id;
|
userId = id;
|
||||||
@event.Set();
|
@event.Set();
|
||||||
};
|
};
|
||||||
|
|
||||||
sut.OnResourceRedirect(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), Mock.Of<IRequest>(), response.Object, ref newUrl);
|
sut.OnResourceRedirect(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), Mock.Of<IRequest>(), response.Object, ref newUrl);
|
||||||
@event.WaitOne();
|
@event.WaitOne();
|
||||||
Assert.AreEqual("some-session-id-123", sessionId);
|
Assert.AreEqual("some-session-id-123", userId);
|
||||||
|
|
||||||
headers.Clear();
|
headers.Clear();
|
||||||
headers.Add("X-LMS-USER-ID", "other-session-id-123");
|
headers.Add("X-LMS-USER-ID", "other-session-id-123");
|
||||||
sessionId = default(string);
|
userId = default;
|
||||||
|
|
||||||
sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
|
sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
|
||||||
@event.WaitOne();
|
@event.WaitOne();
|
||||||
Assert.AreEqual("other-session-id-123", sessionId);
|
Assert.AreEqual("other-session-id-123", userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
@ -264,28 +264,28 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
||||||
var newUrl = default(string);
|
var newUrl = default(string);
|
||||||
var request = new Mock<IRequest>();
|
var request = new Mock<IRequest>();
|
||||||
var response = new Mock<IResponse>();
|
var response = new Mock<IResponse>();
|
||||||
var sessionId = default(string);
|
var userId = default(string);
|
||||||
|
|
||||||
headers.Add("Set-Cookie", "edx-user-info=\"{\\\"username\\\": \\\"edx-123\\\"}\"; expires");
|
headers.Add("Set-Cookie", "edx-user-info=\"{\\\"username\\\": \\\"edx-123\\\"}\"; expires");
|
||||||
request.SetupGet(r => r.Url).Returns("https://www.somelms.org");
|
request.SetupGet(r => r.Url).Returns("https://www.somelms.org");
|
||||||
response.SetupGet(r => r.Headers).Returns(headers);
|
response.SetupGet(r => r.Headers).Returns(headers);
|
||||||
sut.SessionIdentifierDetected += (id) =>
|
sut.UserIdentifierDetected += (id) =>
|
||||||
{
|
{
|
||||||
sessionId = id;
|
userId = id;
|
||||||
@event.Set();
|
@event.Set();
|
||||||
};
|
};
|
||||||
|
|
||||||
sut.OnResourceRedirect(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), Mock.Of<IRequest>(), response.Object, ref newUrl);
|
sut.OnResourceRedirect(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), Mock.Of<IRequest>(), response.Object, ref newUrl);
|
||||||
@event.WaitOne();
|
@event.WaitOne();
|
||||||
Assert.AreEqual("edx-123", sessionId);
|
Assert.AreEqual("edx-123", userId);
|
||||||
|
|
||||||
headers.Clear();
|
headers.Clear();
|
||||||
headers.Add("Set-Cookie", "edx-user-info=\"{\\\"username\\\": \\\"edx-345\\\"}\"; expires");
|
headers.Add("Set-Cookie", "edx-user-info=\"{\\\"username\\\": \\\"edx-345\\\"}\"; expires");
|
||||||
sessionId = default(string);
|
userId = default;
|
||||||
|
|
||||||
sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
|
sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
|
||||||
@event.WaitOne();
|
@event.WaitOne();
|
||||||
Assert.AreEqual("edx-345", sessionId);
|
Assert.AreEqual("edx-345", userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
@ -296,28 +296,28 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
||||||
var newUrl = default(string);
|
var newUrl = default(string);
|
||||||
var request = new Mock<IRequest>();
|
var request = new Mock<IRequest>();
|
||||||
var response = new Mock<IResponse>();
|
var response = new Mock<IResponse>();
|
||||||
var sessionId = default(string);
|
var userId = default(string);
|
||||||
|
|
||||||
headers.Add("Location", "https://www.some-moodle-instance.org/moodle/login/index.php?testsession=123");
|
headers.Add("Location", "https://www.some-moodle-instance.org/moodle/login/index.php?testsession=123");
|
||||||
request.SetupGet(r => r.Url).Returns("https://www.some-moodle-instance.org");
|
request.SetupGet(r => r.Url).Returns("https://www.some-moodle-instance.org");
|
||||||
response.SetupGet(r => r.Headers).Returns(headers);
|
response.SetupGet(r => r.Headers).Returns(headers);
|
||||||
sut.SessionIdentifierDetected += (id) =>
|
sut.UserIdentifierDetected += (id) =>
|
||||||
{
|
{
|
||||||
sessionId = id;
|
userId = id;
|
||||||
@event.Set();
|
@event.Set();
|
||||||
};
|
};
|
||||||
|
|
||||||
sut.OnResourceRedirect(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), Mock.Of<IRequest>(), response.Object, ref newUrl);
|
sut.OnResourceRedirect(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), Mock.Of<IRequest>(), response.Object, ref newUrl);
|
||||||
@event.WaitOne();
|
@event.WaitOne();
|
||||||
Assert.AreEqual("123", sessionId);
|
Assert.AreEqual("123", userId);
|
||||||
|
|
||||||
headers.Clear();
|
headers.Clear();
|
||||||
headers.Add("Location", "https://www.some-moodle-instance.org/moodle/login/index.php?testsession=456");
|
headers.Add("Location", "https://www.some-moodle-instance.org/moodle/login/index.php?testsession=456");
|
||||||
sessionId = default(string);
|
userId = default;
|
||||||
|
|
||||||
sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
|
sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
|
||||||
@event.WaitOne();
|
@event.WaitOne();
|
||||||
Assert.AreEqual("456", sessionId);
|
Assert.AreEqual("456", userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestableResourceHandler : ResourceHandler
|
private class TestableResourceHandler : ResourceHandler
|
||||||
|
|
|
@ -59,9 +59,9 @@ namespace SafeExamBrowser.Browser
|
||||||
public string Tooltip { get; private set; }
|
public string Tooltip { get; private set; }
|
||||||
|
|
||||||
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||||
public event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
|
|
||||||
public event LoseFocusRequestedEventHandler LoseFocusRequested;
|
public event LoseFocusRequestedEventHandler LoseFocusRequested;
|
||||||
public event TerminationRequestedEventHandler TerminationRequested;
|
public event TerminationRequestedEventHandler TerminationRequested;
|
||||||
|
public event UserIdentifierDetectedEventHandler UserIdentifierDetected;
|
||||||
public event WindowsChangedEventHandler WindowsChanged;
|
public event WindowsChangedEventHandler WindowsChanged;
|
||||||
|
|
||||||
public BrowserApplication(
|
public BrowserApplication(
|
||||||
|
@ -209,7 +209,7 @@ namespace SafeExamBrowser.Browser
|
||||||
window.ConfigurationDownloadRequested += (f, a) => ConfigurationDownloadRequested?.Invoke(f, a);
|
window.ConfigurationDownloadRequested += (f, a) => ConfigurationDownloadRequested?.Invoke(f, a);
|
||||||
window.PopupRequested += Window_PopupRequested;
|
window.PopupRequested += Window_PopupRequested;
|
||||||
window.ResetRequested += Window_ResetRequested;
|
window.ResetRequested += Window_ResetRequested;
|
||||||
window.SessionIdentifierDetected += (i) => SessionIdentifierDetected?.Invoke(i);
|
window.UserIdentifierDetected += (i) => UserIdentifierDetected?.Invoke(i);
|
||||||
window.TerminationRequested += () => TerminationRequested?.Invoke();
|
window.TerminationRequested += () => TerminationRequested?.Invoke();
|
||||||
window.LoseFocusRequested += (forward) => LoseFocusRequested?.Invoke(forward);
|
window.LoseFocusRequested += (forward) => LoseFocusRequested?.Invoke(forward);
|
||||||
|
|
||||||
|
|
|
@ -81,11 +81,11 @@ namespace SafeExamBrowser.Browser
|
||||||
|
|
||||||
internal event WindowClosedEventHandler Closed;
|
internal event WindowClosedEventHandler Closed;
|
||||||
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||||
|
internal event LoseFocusRequestedEventHandler LoseFocusRequested;
|
||||||
internal event PopupRequestedEventHandler PopupRequested;
|
internal event PopupRequestedEventHandler PopupRequested;
|
||||||
internal event ResetRequestedEventHandler ResetRequested;
|
internal event ResetRequestedEventHandler ResetRequested;
|
||||||
internal event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
|
|
||||||
internal event LoseFocusRequestedEventHandler LoseFocusRequested;
|
|
||||||
internal event TerminationRequestedEventHandler TerminationRequested;
|
internal event TerminationRequestedEventHandler TerminationRequested;
|
||||||
|
internal event UserIdentifierDetectedEventHandler UserIdentifierDetected;
|
||||||
|
|
||||||
public event IconChangedEventHandler IconChanged;
|
public event IconChangedEventHandler IconChanged;
|
||||||
public event TitleChangedEventHandler TitleChanged;
|
public event TitleChangedEventHandler TitleChanged;
|
||||||
|
@ -185,9 +185,9 @@ namespace SafeExamBrowser.Browser
|
||||||
keyboardHandler.ZoomInRequested += ZoomInRequested;
|
keyboardHandler.ZoomInRequested += ZoomInRequested;
|
||||||
keyboardHandler.ZoomOutRequested += ZoomOutRequested;
|
keyboardHandler.ZoomOutRequested += ZoomOutRequested;
|
||||||
keyboardHandler.ZoomResetRequested += ZoomResetRequested;
|
keyboardHandler.ZoomResetRequested += ZoomResetRequested;
|
||||||
resourceHandler.SessionIdentifierDetected += (id) => SessionIdentifierDetected?.Invoke(id);
|
|
||||||
requestHandler.QuitUrlVisited += RequestHandler_QuitUrlVisited;
|
requestHandler.QuitUrlVisited += RequestHandler_QuitUrlVisited;
|
||||||
requestHandler.RequestBlocked += RequestHandler_RequestBlocked;
|
requestHandler.RequestBlocked += RequestHandler_RequestBlocked;
|
||||||
|
resourceHandler.UserIdentifierDetected += (id) => UserIdentifierDetected?.Invoke(id);
|
||||||
|
|
||||||
InitializeRequestFilter(requestFilter);
|
InitializeRequestFilter(requestFilter);
|
||||||
|
|
||||||
|
|
|
@ -44,9 +44,9 @@ namespace SafeExamBrowser.Browser.Handlers
|
||||||
|
|
||||||
private IResourceHandler contentHandler;
|
private IResourceHandler contentHandler;
|
||||||
private IResourceHandler pageHandler;
|
private IResourceHandler pageHandler;
|
||||||
private string sessionIdentifier;
|
private string userIdentifier;
|
||||||
|
|
||||||
internal event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
|
internal event UserIdentifierDetectedEventHandler UserIdentifierDetected;
|
||||||
|
|
||||||
internal ResourceHandler(
|
internal ResourceHandler(
|
||||||
AppConfig appConfig,
|
AppConfig appConfig,
|
||||||
|
@ -100,7 +100,7 @@ namespace SafeExamBrowser.Browser.Handlers
|
||||||
{
|
{
|
||||||
if (sessionMode == SessionMode.Server)
|
if (sessionMode == SessionMode.Server)
|
||||||
{
|
{
|
||||||
SearchSessionIdentifiers(request, response);
|
SearchUserIdentifier(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnResourceRedirect(chromiumWebBrowser, browser, frame, request, response, ref newUrl);
|
base.OnResourceRedirect(chromiumWebBrowser, browser, frame, request, response, ref newUrl);
|
||||||
|
@ -117,7 +117,7 @@ namespace SafeExamBrowser.Browser.Handlers
|
||||||
|
|
||||||
if (sessionMode == SessionMode.Server)
|
if (sessionMode == SessionMode.Server)
|
||||||
{
|
{
|
||||||
SearchSessionIdentifiers(request, response);
|
SearchUserIdentifier(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.OnResourceResponse(webBrowser, browser, frame, request, response);
|
return base.OnResourceResponse(webBrowser, browser, frame, request, response);
|
||||||
|
@ -233,41 +233,46 @@ namespace SafeExamBrowser.Browser.Handlers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SearchSessionIdentifiers(IRequest request, IResponse response)
|
private void SearchUserIdentifier(IRequest request, IResponse response)
|
||||||
{
|
{
|
||||||
var success = TrySearchGenericSessionIdentifier(response);
|
var success = TrySearchGenericUserIdentifier(response);
|
||||||
|
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
SearchEdxIdentifier(response);
|
success = TrySearchEdxUserIdentifier(response);
|
||||||
SearchMoodleIdentifier(request, response);
|
}
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
TrySearchMoodleUserIdentifier(request, response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TrySearchGenericSessionIdentifier(IResponse response)
|
private bool TrySearchGenericUserIdentifier(IResponse response)
|
||||||
{
|
{
|
||||||
var ids = response.Headers.GetValues("X-LMS-USER-ID");
|
var ids = response.Headers.GetValues("X-LMS-USER-ID");
|
||||||
|
var success = false;
|
||||||
|
|
||||||
if (ids != default(string[]))
|
if (ids != default(string[]))
|
||||||
{
|
{
|
||||||
var userId = ids.FirstOrDefault();
|
var userId = ids.FirstOrDefault();
|
||||||
|
|
||||||
if (userId != default && sessionIdentifier != userId)
|
if (userId != default && userIdentifier != userId)
|
||||||
{
|
{
|
||||||
sessionIdentifier = userId;
|
userIdentifier = userId;
|
||||||
Task.Run(() => SessionIdentifierDetected?.Invoke(sessionIdentifier));
|
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
|
||||||
logger.Info("Generic LMS session detected.");
|
logger.Info("Generic LMS user identifier detected.");
|
||||||
|
success = true;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SearchEdxIdentifier(IResponse response)
|
private bool TrySearchEdxUserIdentifier(IResponse response)
|
||||||
{
|
{
|
||||||
var cookies = response.Headers.GetValues("Set-Cookie");
|
var cookies = response.Headers.GetValues("Set-Cookie");
|
||||||
|
var success = false;
|
||||||
|
|
||||||
if (cookies != default(string[]))
|
if (cookies != default(string[]))
|
||||||
{
|
{
|
||||||
|
@ -284,32 +289,42 @@ namespace SafeExamBrowser.Browser.Handlers
|
||||||
var json = JsonConvert.DeserializeObject(sanitized) as JObject;
|
var json = JsonConvert.DeserializeObject(sanitized) as JObject;
|
||||||
var userName = json["username"].Value<string>();
|
var userName = json["username"].Value<string>();
|
||||||
|
|
||||||
if (sessionIdentifier != userName)
|
if (userIdentifier != userName)
|
||||||
{
|
{
|
||||||
sessionIdentifier = userName;
|
userIdentifier = userName;
|
||||||
Task.Run(() => SessionIdentifierDetected?.Invoke(sessionIdentifier));
|
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
|
||||||
logger.Info("EdX session detected.");
|
logger.Info("EdX user identifier detected.");
|
||||||
|
success = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
logger.Error("Failed to parse edX session identifier!", e);
|
logger.Error("Failed to parse edX user identifier!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SearchMoodleIdentifier(IRequest request, IResponse response)
|
private bool TrySearchMoodleUserIdentifier(IRequest request, IResponse response)
|
||||||
{
|
{
|
||||||
var success = TrySearchByLocation(response);
|
var success = TrySearchMoodleUserIdentifierByLocation(response);
|
||||||
|
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
TrySearchBySession(request, response);
|
success = TrySearchMoodleUserIdentifierByRequest(MoodleRequestType.Plugin, request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
success = TrySearchMoodleUserIdentifierByRequest(MoodleRequestType.Theme, request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TrySearchByLocation(IResponse response)
|
private bool TrySearchMoodleUserIdentifierByLocation(IResponse response)
|
||||||
{
|
{
|
||||||
var locations = response.Headers.GetValues("Location");
|
var locations = response.Headers.GetValues("Location");
|
||||||
|
|
||||||
|
@ -323,11 +338,11 @@ namespace SafeExamBrowser.Browser.Handlers
|
||||||
{
|
{
|
||||||
var userId = location.Substring(location.IndexOf("=") + 1);
|
var userId = location.Substring(location.IndexOf("=") + 1);
|
||||||
|
|
||||||
if (sessionIdentifier != userId)
|
if (userIdentifier != userId)
|
||||||
{
|
{
|
||||||
sessionIdentifier = userId;
|
userIdentifier = userId;
|
||||||
Task.Run(() => SessionIdentifierDetected?.Invoke(sessionIdentifier));
|
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
|
||||||
logger.Info("Moodle session detected.");
|
logger.Info("Moodle user identifier detected by location.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -335,16 +350,17 @@ namespace SafeExamBrowser.Browser.Handlers
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
logger.Error("Failed to parse Moodle session identifier!", e);
|
logger.Error("Failed to parse Moodle user identifier by location!", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TrySearchBySession(IRequest request, IResponse response)
|
private bool TrySearchMoodleUserIdentifierByRequest(MoodleRequestType type, IRequest request, IResponse response)
|
||||||
{
|
{
|
||||||
var cookies = response.Headers.GetValues("Set-Cookie");
|
var cookies = response.Headers.GetValues("Set-Cookie");
|
||||||
|
var success = false;
|
||||||
|
|
||||||
if (cookies != default(string[]))
|
if (cookies != default(string[]))
|
||||||
{
|
{
|
||||||
|
@ -352,51 +368,83 @@ namespace SafeExamBrowser.Browser.Handlers
|
||||||
|
|
||||||
if (session != default)
|
if (session != default)
|
||||||
{
|
{
|
||||||
var requestUrl = request.Url;
|
var userId = ExecuteMoodleUserIdentifierRequest(request.Url, session, type);
|
||||||
|
|
||||||
Task.Run(async () =>
|
if (int.TryParse(userId, out var id) && id > 0 && userIdentifier != userId)
|
||||||
{
|
{
|
||||||
try
|
userIdentifier = userId;
|
||||||
{
|
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
|
||||||
var start = session.IndexOf("=") + 1;
|
logger.Info($"Moodle user identifier detected by request ({type}).");
|
||||||
var end = session.IndexOf(";");
|
success = true;
|
||||||
var value = session.Substring(start, end - start);
|
}
|
||||||
var uri = new Uri(requestUrl);
|
|
||||||
var message = new HttpRequestMessage(HttpMethod.Get, $"{uri.Scheme}{Uri.SchemeDelimiter}{uri.Host}/theme/boost_ethz/sebuser.php");
|
|
||||||
|
|
||||||
using (var handler = new HttpClientHandler { UseCookies = false })
|
|
||||||
using (var client = new HttpClient(handler))
|
|
||||||
{
|
|
||||||
message.Headers.Add("Cookie", $"MoodleSession={value}");
|
|
||||||
|
|
||||||
var result = await client.SendAsync(message);
|
|
||||||
|
|
||||||
if (result.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
var userId = await result.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
if (int.TryParse(userId, out var id) && id > 0 && sessionIdentifier != userId)
|
|
||||||
{
|
|
||||||
#pragma warning disable CS4014
|
|
||||||
sessionIdentifier = userId;
|
|
||||||
Task.Run(() => SessionIdentifierDetected?.Invoke(sessionIdentifier));
|
|
||||||
logger.Info("Moodle session detected.");
|
|
||||||
#pragma warning restore CS4014
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.Error($"Failed to retrieve Moodle session identifier! Response: {result.StatusCode} {result.ReasonPhrase}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
logger.Error("Failed to parse Moodle session identifier!", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ExecuteMoodleUserIdentifierRequest(string requestUrl, string session, MoodleRequestType type)
|
||||||
|
{
|
||||||
|
var userId = default(string);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var endpointUrl = default(string);
|
||||||
|
var start = session.IndexOf("=") + 1;
|
||||||
|
var end = session.IndexOf(";");
|
||||||
|
var value = session.Substring(start, end - start);
|
||||||
|
var uri = new Uri(requestUrl);
|
||||||
|
|
||||||
|
if (type == MoodleRequestType.Plugin)
|
||||||
|
{
|
||||||
|
endpointUrl = $"{uri.Scheme}{Uri.SchemeDelimiter}{uri.Host}/mod/quiz/accessrule/sebserver/classes/external/user.php";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
endpointUrl = $"{uri.Scheme}{Uri.SchemeDelimiter}{uri.Host}/theme/boost_ethz/sebuser.php";
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = new HttpRequestMessage(HttpMethod.Get, endpointUrl);
|
||||||
|
|
||||||
|
using (var handler = new HttpClientHandler { UseCookies = false })
|
||||||
|
using (var client = new HttpClient(handler))
|
||||||
|
{
|
||||||
|
message.Headers.Add("Cookie", $"MoodleSession={value}");
|
||||||
|
|
||||||
|
var result = await client.SendAsync(message);
|
||||||
|
|
||||||
|
if (result.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
userId = await result.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
else if (result.StatusCode != HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
logger.Error($"Failed to retrieve Moodle user identifier by request ({type})! Response: {(int) result.StatusCode} {result.ReasonPhrase}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.Error($"Failed to parse Moodle user identifier by request ({type})!", e);
|
||||||
|
}
|
||||||
|
}).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.Error($"Failed to execute Moodle user identifier request ({type})!", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum MoodleRequestType
|
||||||
|
{
|
||||||
|
Plugin,
|
||||||
|
Theme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -324,18 +324,18 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Browser_MustHandleSessionIdentifierDetection()
|
public void Browser_MustHandleUserIdentifierDetection()
|
||||||
{
|
{
|
||||||
var counter = 0;
|
var counter = 0;
|
||||||
var identifier = "abc123";
|
var identifier = "abc123";
|
||||||
|
|
||||||
settings.SessionMode = SessionMode.Server;
|
settings.SessionMode = SessionMode.Server;
|
||||||
server.Setup(s => s.SendSessionIdentifier(It.IsAny<string>())).Returns(() => new ServerResponse(++counter == 3));
|
server.Setup(s => s.SendUserIdentifier(It.IsAny<string>())).Returns(() => new ServerResponse(++counter == 3));
|
||||||
|
|
||||||
sut.TryStart();
|
sut.TryStart();
|
||||||
browser.Raise(b => b.SessionIdentifierDetected += null, identifier);
|
browser.Raise(b => b.UserIdentifierDetected += null, identifier);
|
||||||
|
|
||||||
server.Verify(s => s.SendSessionIdentifier(It.Is<string>(id => id == identifier)), Times.Exactly(3));
|
server.Verify(s => s.SendUserIdentifier(It.Is<string>(id => id == identifier)), Times.Exactly(3));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
|
@ -200,9 +200,9 @@ namespace SafeExamBrowser.Client
|
||||||
applicationMonitor.ExplorerStarted += ApplicationMonitor_ExplorerStarted;
|
applicationMonitor.ExplorerStarted += ApplicationMonitor_ExplorerStarted;
|
||||||
applicationMonitor.TerminationFailed += ApplicationMonitor_TerminationFailed;
|
applicationMonitor.TerminationFailed += ApplicationMonitor_TerminationFailed;
|
||||||
Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested;
|
Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested;
|
||||||
Browser.SessionIdentifierDetected += Browser_SessionIdentifierDetected;
|
|
||||||
Browser.TerminationRequested += Browser_TerminationRequested;
|
|
||||||
Browser.LoseFocusRequested += Browser_LoseFocusRequested;
|
Browser.LoseFocusRequested += Browser_LoseFocusRequested;
|
||||||
|
Browser.TerminationRequested += Browser_TerminationRequested;
|
||||||
|
Browser.UserIdentifierDetected += Browser_UserIdentifierDetected;
|
||||||
ClientHost.ExamSelectionRequested += ClientHost_ExamSelectionRequested;
|
ClientHost.ExamSelectionRequested += ClientHost_ExamSelectionRequested;
|
||||||
ClientHost.MessageBoxRequested += ClientHost_MessageBoxRequested;
|
ClientHost.MessageBoxRequested += ClientHost_MessageBoxRequested;
|
||||||
ClientHost.PasswordRequested += ClientHost_PasswordRequested;
|
ClientHost.PasswordRequested += ClientHost_PasswordRequested;
|
||||||
|
@ -254,9 +254,9 @@ namespace SafeExamBrowser.Client
|
||||||
if (Browser != null)
|
if (Browser != null)
|
||||||
{
|
{
|
||||||
Browser.ConfigurationDownloadRequested -= Browser_ConfigurationDownloadRequested;
|
Browser.ConfigurationDownloadRequested -= Browser_ConfigurationDownloadRequested;
|
||||||
Browser.SessionIdentifierDetected -= Browser_SessionIdentifierDetected;
|
|
||||||
Browser.TerminationRequested -= Browser_TerminationRequested;
|
|
||||||
Browser.LoseFocusRequested -= Browser_LoseFocusRequested;
|
Browser.LoseFocusRequested -= Browser_LoseFocusRequested;
|
||||||
|
Browser.TerminationRequested -= Browser_TerminationRequested;
|
||||||
|
Browser.UserIdentifierDetected -= Browser_UserIdentifierDetected;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ClientHost != null)
|
if (ClientHost != null)
|
||||||
|
@ -531,17 +531,17 @@ namespace SafeExamBrowser.Client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Browser_SessionIdentifierDetected(string identifier)
|
private void Browser_UserIdentifierDetected(string identifier)
|
||||||
{
|
{
|
||||||
if (Settings.SessionMode == SessionMode.Server)
|
if (Settings.SessionMode == SessionMode.Server)
|
||||||
{
|
{
|
||||||
var response = Server.SendSessionIdentifier(identifier);
|
var response = Server.SendUserIdentifier(identifier);
|
||||||
|
|
||||||
while (!response.Success)
|
while (!response.Success)
|
||||||
{
|
{
|
||||||
logger.Error($"Failed to communicate session identifier with server! {response.Message}");
|
logger.Error($"Failed to communicate user identifier with server! {response.Message}");
|
||||||
Thread.Sleep(Settings.Server.RequestAttemptInterval);
|
Thread.Sleep(Settings.Server.RequestAttemptInterval);
|
||||||
response = Server.SendSessionIdentifier(identifier);
|
response = Server.SendUserIdentifier(identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,9 +110,9 @@ namespace SafeExamBrowser.Server.Contracts
|
||||||
ServerResponse<string> SendSelectedExam(Exam exam);
|
ServerResponse<string> SendSelectedExam(Exam exam);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends the given user session identifier of a LMS and thus establishes a connection with the server.
|
/// Sends the given user identifier of an LMS and thus establishes a connection with the server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ServerResponse SendSessionIdentifier(string identifier);
|
ServerResponse SendUserIdentifier(string identifier);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts sending ping and log data to the server.
|
/// Starts sending ping and log data to the server.
|
||||||
|
|
|
@ -13,9 +13,9 @@ using SafeExamBrowser.Settings.Server;
|
||||||
|
|
||||||
namespace SafeExamBrowser.Server.Requests
|
namespace SafeExamBrowser.Server.Requests
|
||||||
{
|
{
|
||||||
internal class SessionIdentifierRequest : BaseRequest
|
internal class UserIdentifierRequest : BaseRequest
|
||||||
{
|
{
|
||||||
internal SessionIdentifierRequest(
|
internal UserIdentifierRequest(
|
||||||
ApiVersion1 api,
|
ApiVersion1 api,
|
||||||
HttpClient httpClient,
|
HttpClient httpClient,
|
||||||
ILogger logger,
|
ILogger logger,
|
|
@ -85,7 +85,7 @@
|
||||||
<Compile Include="Requests\PowerSupplyRequest.cs" />
|
<Compile Include="Requests\PowerSupplyRequest.cs" />
|
||||||
<Compile Include="Requests\RaiseHandRequest.cs" />
|
<Compile Include="Requests\RaiseHandRequest.cs" />
|
||||||
<Compile Include="Requests\SelectExamRequest.cs" />
|
<Compile Include="Requests\SelectExamRequest.cs" />
|
||||||
<Compile Include="Requests\SessionIdentifierRequest.cs" />
|
<Compile Include="Requests\UserIdentifierRequest.cs" />
|
||||||
<Compile Include="ServerProxy.cs" />
|
<Compile Include="ServerProxy.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -310,18 +310,18 @@ namespace SafeExamBrowser.Server
|
||||||
return new ServerResponse<string>(success, browserExamKey, message);
|
return new ServerResponse<string>(success, browserExamKey, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerResponse SendSessionIdentifier(string identifier)
|
public ServerResponse SendUserIdentifier(string identifier)
|
||||||
{
|
{
|
||||||
var request = new SessionIdentifierRequest(api, httpClient, logger, parser, settings);
|
var request = new UserIdentifierRequest(api, httpClient, logger, parser, settings);
|
||||||
var success = request.TryExecute(examId, identifier, out var message);
|
var success = request.TryExecute(examId, identifier, out var message);
|
||||||
|
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
logger.Info("Successfully sent session identifier.");
|
logger.Info("Successfully sent user identifier.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.Error("Failed to send session identifier!");
|
logger.Error("Failed to send user identifier!");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ServerResponse(success, message);
|
return new ServerResponse(success, message);
|
||||||
|
|
Loading…
Reference in a new issue