Restore SEBPatch
This commit is contained in:
414
SafeExamBrowser.Server/Parser.cs
Normal file
414
SafeExamBrowser.Server/Parser.cs
Normal file
@@ -0,0 +1,414 @@
|
||||
/*
|
||||
* 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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts.Data;
|
||||
using SafeExamBrowser.Server.Contracts.Events.Proctoring;
|
||||
using SafeExamBrowser.Server.Data;
|
||||
using SafeExamBrowser.Server.Requests;
|
||||
|
||||
namespace SafeExamBrowser.Server
|
||||
{
|
||||
internal class Parser
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
|
||||
internal Parser(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
internal bool IsTokenExpired(HttpContent content)
|
||||
{
|
||||
var isExpired = false;
|
||||
|
||||
try
|
||||
{
|
||||
var json = JsonConvert.DeserializeObject(Extract(content)) as JObject;
|
||||
var error = json["error"].Value<string>();
|
||||
|
||||
isExpired = error?.Equals("invalid_token", StringComparison.OrdinalIgnoreCase) == true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse token expiration content!", e);
|
||||
}
|
||||
|
||||
return isExpired;
|
||||
}
|
||||
|
||||
internal bool TryParseApi(HttpContent content, out ApiVersion1 api)
|
||||
{
|
||||
var success = false;
|
||||
|
||||
api = new ApiVersion1();
|
||||
|
||||
try
|
||||
{
|
||||
var json = JsonConvert.DeserializeObject(Extract(content)) as JObject;
|
||||
var apisJson = json["api-versions"];
|
||||
|
||||
foreach (var apiJson in apisJson.AsJEnumerable())
|
||||
{
|
||||
if (apiJson["name"].Value<string>().Equals("v1"))
|
||||
{
|
||||
foreach (var endpoint in apiJson["endpoints"].AsJEnumerable())
|
||||
{
|
||||
var name = endpoint["name"].Value<string>();
|
||||
var location = endpoint["location"].Value<string>();
|
||||
|
||||
switch (name)
|
||||
{
|
||||
case "access-token-endpoint":
|
||||
api.AccessTokenEndpoint = location;
|
||||
break;
|
||||
case "seb-configuration-endpoint":
|
||||
api.ConfigurationEndpoint = location;
|
||||
break;
|
||||
case "seb-handshake-endpoint":
|
||||
api.HandshakeEndpoint = location;
|
||||
break;
|
||||
case "seb-log-endpoint":
|
||||
api.LogEndpoint = location;
|
||||
break;
|
||||
case "seb-ping-endpoint":
|
||||
api.PingEndpoint = location;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
success = true;
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
logger.Error("The selected SEB server instance does not support the required API version!");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse server API!", e);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
internal bool TryParseAppSignatureKeySalt(HttpResponseMessage response, out string salt)
|
||||
{
|
||||
salt = default;
|
||||
|
||||
try
|
||||
{
|
||||
var hasHeader = response.Headers.TryGetValues(Header.APP_SIGNATURE_KEY_SALT, out var values);
|
||||
|
||||
if (hasHeader)
|
||||
{
|
||||
salt = values.First();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse app signature key salt!", e);
|
||||
}
|
||||
|
||||
return salt != default;
|
||||
}
|
||||
|
||||
internal bool TryParseBrowserExamKey(HttpResponseMessage response, out string browserExamKey)
|
||||
{
|
||||
browserExamKey = default;
|
||||
|
||||
try
|
||||
{
|
||||
var hasHeader = response.Headers.TryGetValues(Header.BROWSER_EXAM_KEY, out var values);
|
||||
|
||||
if (hasHeader)
|
||||
{
|
||||
browserExamKey = values.First();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse browser exam key!", e);
|
||||
}
|
||||
|
||||
return browserExamKey != default;
|
||||
}
|
||||
|
||||
internal bool TryParseConnectionToken(HttpResponseMessage response, out string connectionToken)
|
||||
{
|
||||
connectionToken = default;
|
||||
|
||||
try
|
||||
{
|
||||
var hasHeader = response.Headers.TryGetValues(Header.CONNECTION_TOKEN, out var values);
|
||||
|
||||
if (hasHeader)
|
||||
{
|
||||
connectionToken = values.First();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to retrieve connection token!");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse connection token!", e);
|
||||
}
|
||||
|
||||
return connectionToken != default;
|
||||
}
|
||||
|
||||
internal bool TryParseExams(HttpContent content, out IEnumerable<Exam> exams)
|
||||
{
|
||||
var list = new List<Exam>();
|
||||
|
||||
try
|
||||
{
|
||||
var json = JsonConvert.DeserializeObject(Extract(content)) as JArray;
|
||||
|
||||
foreach (var exam in json.AsJEnumerable())
|
||||
{
|
||||
list.Add(new Exam
|
||||
{
|
||||
Id = exam["examId"].Value<string>(),
|
||||
LmsName = exam["lmsType"].Value<string>(),
|
||||
Name = exam["name"].Value<string>(),
|
||||
Url = exam["url"].Value<string>()
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse exams!", e);
|
||||
}
|
||||
|
||||
exams = list;
|
||||
|
||||
return exams.Any();
|
||||
}
|
||||
|
||||
internal bool TryParseInstruction(HttpContent content, out Attributes attributes, out string instruction, out string instructionConfirmation)
|
||||
{
|
||||
attributes = new Attributes();
|
||||
instruction = default;
|
||||
instructionConfirmation = default;
|
||||
|
||||
try
|
||||
{
|
||||
var json = JsonConvert.DeserializeObject(Extract(content)) as JObject;
|
||||
|
||||
if (json != default(JObject))
|
||||
{
|
||||
instruction = json["instruction"].Value<string>();
|
||||
|
||||
if (json.ContainsKey("attributes"))
|
||||
{
|
||||
var attributesJson = json["attributes"] as JObject;
|
||||
|
||||
if (attributesJson.ContainsKey("instruction-confirm"))
|
||||
{
|
||||
instructionConfirmation = attributesJson["instruction-confirm"].Value<string>();
|
||||
}
|
||||
|
||||
attributes = ParseAttributes(attributesJson, instruction);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse instruction!", e);
|
||||
}
|
||||
|
||||
return instruction != default;
|
||||
}
|
||||
|
||||
internal bool TryParseOauth2Token(HttpContent content, out string oauth2Token)
|
||||
{
|
||||
oauth2Token = default;
|
||||
|
||||
try
|
||||
{
|
||||
var json = JsonConvert.DeserializeObject(Extract(content)) as JObject;
|
||||
|
||||
oauth2Token = json["access_token"].Value<string>();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse Oauth2 token!", e);
|
||||
}
|
||||
|
||||
return oauth2Token != default;
|
||||
}
|
||||
|
||||
private Attributes ParseAttributes(JObject attributesJson, string instruction)
|
||||
{
|
||||
var attributes = new Attributes();
|
||||
|
||||
switch (instruction)
|
||||
{
|
||||
case Instructions.LOCK_SCREEN:
|
||||
ParseLockScreenInstruction(attributes, attributesJson);
|
||||
break;
|
||||
case Instructions.NOTIFICATION_CONFIRM:
|
||||
ParseNotificationConfirmation(attributes, attributesJson);
|
||||
break;
|
||||
case Instructions.PROCTORING:
|
||||
ParseProctoringInstruction(attributes, attributesJson);
|
||||
break;
|
||||
case Instructions.PROCTORING_RECONFIGURATION:
|
||||
ParseReconfigurationInstruction(attributes, attributesJson);
|
||||
break;
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
private void ParseLockScreenInstruction(Attributes attributes, JObject attributesJson)
|
||||
{
|
||||
if (attributesJson.ContainsKey("message"))
|
||||
{
|
||||
attributes.Message = attributesJson["message"].Value<string>();
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseNotificationConfirmation(Attributes attributes, JObject attributesJson)
|
||||
{
|
||||
if (attributesJson.ContainsKey("id"))
|
||||
{
|
||||
attributes.Id = attributesJson["id"].Value<int>();
|
||||
}
|
||||
|
||||
if (attributesJson.ContainsKey("type"))
|
||||
{
|
||||
switch (attributesJson["type"].Value<string>())
|
||||
{
|
||||
case "lockscreen":
|
||||
attributes.Type = AttributeType.LockScreen;
|
||||
break;
|
||||
case "raisehand":
|
||||
attributes.Type = AttributeType.Hand;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseProctoringInstruction(Attributes attributes, JObject attributesJson)
|
||||
{
|
||||
var provider = attributesJson["service-type"].Value<string>();
|
||||
|
||||
switch (provider)
|
||||
{
|
||||
case "JITSI_MEET":
|
||||
attributes.Instruction = ParseJitsiMeetInstruction(attributesJson);
|
||||
break;
|
||||
case "SCREEN_PROCTORING":
|
||||
attributes.Instruction = ParseScreenProctoringInstruction(attributesJson);
|
||||
break;
|
||||
case "ZOOM":
|
||||
attributes.Instruction = ParseZoomInstruction(attributesJson);
|
||||
break;
|
||||
}
|
||||
|
||||
if (attributes.Instruction != default)
|
||||
{
|
||||
attributes.Instruction.Method = attributesJson["method"].Value<string>() == "JOIN" ? InstructionMethod.Join : InstructionMethod.Leave;
|
||||
}
|
||||
}
|
||||
|
||||
private JitsiMeetInstruction ParseJitsiMeetInstruction(JObject attributesJson)
|
||||
{
|
||||
return new JitsiMeetInstruction
|
||||
{
|
||||
RoomName = attributesJson["jitsiMeetRoom"].Value<string>(),
|
||||
ServerUrl = attributesJson["jitsiMeetServerURL"].Value<string>(),
|
||||
Token = attributesJson["jitsiMeetToken"].Value<string>()
|
||||
};
|
||||
}
|
||||
|
||||
private ScreenProctoringInstruction ParseScreenProctoringInstruction(JObject attributesJson)
|
||||
{
|
||||
return new ScreenProctoringInstruction
|
||||
{
|
||||
ClientId = attributesJson["screenProctoringClientId"].Value<string>(),
|
||||
ClientSecret = attributesJson["screenProctoringClientSecret"].Value<string>(),
|
||||
GroupId = attributesJson["screenProctoringGroupId"].Value<string>(),
|
||||
ServiceUrl = attributesJson["screenProctoringServiceURL"].Value<string>(),
|
||||
SessionId = attributesJson["screenProctoringClientSessionId"].Value<string>()
|
||||
};
|
||||
}
|
||||
|
||||
private ZoomInstruction ParseZoomInstruction(JObject attributesJson)
|
||||
{
|
||||
return new ZoomInstruction
|
||||
{
|
||||
MeetingNumber = attributesJson["zoomRoom"].Value<string>(),
|
||||
Password = attributesJson["zoomMeetingKey"].Value<string>(),
|
||||
SdkKey = attributesJson["zoomAPIKey"].Value<string>(),
|
||||
Signature = attributesJson["zoomToken"].Value<string>(),
|
||||
Subject = attributesJson["zoomSubject"].Value<string>(),
|
||||
UserName = attributesJson["zoomUserName"].Value<string>()
|
||||
};
|
||||
}
|
||||
|
||||
private void ParseReconfigurationInstruction(Attributes attributes, JObject attributesJson)
|
||||
{
|
||||
if (attributesJson.ContainsKey("jitsiMeetFeatureFlagChat"))
|
||||
{
|
||||
attributes.AllowChat = attributesJson["jitsiMeetFeatureFlagChat"].Value<bool>();
|
||||
}
|
||||
|
||||
if (attributesJson.ContainsKey("zoomFeatureFlagChat"))
|
||||
{
|
||||
attributes.AllowChat = attributesJson["zoomFeatureFlagChat"].Value<bool>();
|
||||
}
|
||||
|
||||
if (attributesJson.ContainsKey("jitsiMeetReceiveAudio"))
|
||||
{
|
||||
attributes.ReceiveAudio = attributesJson["jitsiMeetReceiveAudio"].Value<bool>();
|
||||
}
|
||||
|
||||
if (attributesJson.ContainsKey("zoomReceiveAudio"))
|
||||
{
|
||||
attributes.ReceiveAudio = attributesJson["zoomReceiveAudio"].Value<bool>();
|
||||
}
|
||||
|
||||
if (attributesJson.ContainsKey("jitsiMeetReceiveVideo"))
|
||||
{
|
||||
attributes.ReceiveVideo = attributesJson["jitsiMeetReceiveVideo"].Value<bool>();
|
||||
}
|
||||
|
||||
if (attributesJson.ContainsKey("zoomReceiveVideo"))
|
||||
{
|
||||
attributes.ReceiveVideo = attributesJson["zoomReceiveVideo"].Value<bool>();
|
||||
}
|
||||
}
|
||||
|
||||
private string Extract(HttpContent content)
|
||||
{
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
return await content.ReadAsStreamAsync();
|
||||
});
|
||||
var stream = task.GetAwaiter().GetResult();
|
||||
var reader = new StreamReader(stream);
|
||||
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user