SEBWIN-494: Fixed issue with sensitive proctoring data.

This commit is contained in:
Damian Büchel 2021-06-29 09:44:16 +02:00
parent d141b747eb
commit ee2133c0c2
6 changed files with 187 additions and 127 deletions

View file

@ -6,46 +6,59 @@
<div id="placeholder" />
<script src='https://meet.jit.si/external_api.js'></script>
<script type="text/javascript">
var configOverwrite = {
disableProfile: true,
startAudioOnly: '%_AUDIO_ONLY_%',
startWithAudioMuted: '%_AUDIO_MUTED_%',
startWithVideoMuted: '%_VIDEO_MUTED_%'
};
var interfaceOverwrite = {
JITSI_WATERMARK_LINK: '',
SHOW_JITSI_WATERMARK: false,
TOOLBAR_BUTTONS: [
'microphone', 'camera', '%%_ALLOW_CLOSED_CAPTIONS_%%', /*'desktop',*/ 'embedmeeting', 'fullscreen',
'fodeviceselection', /*'hangup',*/ 'profile', '%%_ALLOW_CHAT_%%', '%%_ALLOW_RECORDING_%%',
'livestreaming', 'etherpad', /*'sharedvideo',*/ 'settings', '%%_ALLOW_RAISE_HAND_%%',
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
'%%_ALLOW_TILE_VIEW_%%', 'select-background', 'download', 'help', /*'mute-everyone',*/ 'mute-video-everyone', 'security'
]
};
var options = {
configOverwrite: configOverwrite,
height: "100%",
interfaceConfigOverwrite: interfaceOverwrite,
jwt: "%%_TOKEN_%%",
parentNode: document.querySelector('#placeholder'),
roomName: "%%_ROOM_NAME_%%",
width: "100%"
};
var api = new JitsiMeetExternalAPI("%%_DOMAIN_%%", options);
var api;
api.addListener('audioMuteStatusChanged', args => {
if (args.muted) {
api.executeCommand('toggleAudio');
}
});
api.addListener('videoMuteStatusChanged', args => {
if (args.muted) {
api.executeCommand('toggleVideo');
}
});
function startMeeting(credentials) {
var configOverwrite = {
disableProfile: true,
startAudioOnly: '%_AUDIO_ONLY_%',
startWithAudioMuted: '%_AUDIO_MUTED_%',
startWithVideoMuted: '%_VIDEO_MUTED_%'
};
var interfaceOverwrite = {
JITSI_WATERMARK_LINK: '',
SHOW_JITSI_WATERMARK: false,
TOOLBAR_BUTTONS: [
'microphone', 'camera', '%%_ALLOW_CLOSED_CAPTIONS_%%', /*'desktop',*/ 'embedmeeting', 'fullscreen',
'fodeviceselection', /*'hangup',*/ 'profile', '%%_ALLOW_CHAT_%%', '%%_ALLOW_RECORDING_%%',
'livestreaming', 'etherpad', /*'sharedvideo',*/ 'settings', '%%_ALLOW_RAISE_HAND_%%',
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
'%%_ALLOW_TILE_VIEW_%%', 'select-background', 'download', 'help', /*'mute-everyone',*/ 'mute-video-everyone', 'security'
]
};
var options = {
configOverwrite: configOverwrite,
height: '100%',
interfaceConfigOverwrite: interfaceOverwrite,
jwt: credentials.token,
parentNode: document.querySelector('#placeholder'),
roomName: credentials.roomName,
width: '100%'
};
api = new JitsiMeetExternalAPI(credentials.domain, options);
api.addListener('audioMuteStatusChanged', args => {
if (args.muted) {
api.executeCommand('toggleAudio');
}
});
api.addListener('videoMuteStatusChanged', args => {
if (args.muted) {
api.executeCommand('toggleVideo');
}
});
api.executeCommand("subject", "%%_SUBJECT_%%");
api.executeCommand('subject', credentials.subject);
}
function webMessageReceived(args) {
if ('credentials' in args.data) {
startMeeting(args.data.credentials);
}
}
window.chrome.webview.addEventListener('message', webMessageReceived);
window.chrome.webview.postMessage('credentials');
</script>
</body>
</html>

View file

@ -8,7 +8,9 @@
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;
using Newtonsoft.Json.Linq;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.UserInterface.Contracts.Proctoring;
using SafeExamBrowser.UserInterface.Contracts.Proctoring.Events;
@ -17,12 +19,15 @@ namespace SafeExamBrowser.Proctoring
internal class ProctoringControl : WebView2, IProctoringControl
{
private readonly ILogger logger;
private readonly ProctoringSettings settings;
public event FullScreenChangedEventHandler FullScreenChanged;
internal ProctoringControl(ILogger logger)
internal ProctoringControl(ILogger logger, ProctoringSettings settings)
{
this.logger = logger;
this.settings = settings;
CoreWebView2InitializationCompleted += ProctoringControl_CoreWebView2InitializationCompleted;
}
@ -35,6 +40,7 @@ namespace SafeExamBrowser.Proctoring
CoreWebView2.Settings.IsStatusBarEnabled = false;
CoreWebView2.ContainsFullScreenElementChanged += CoreWebView2_ContainsFullScreenElementChanged;
CoreWebView2.PermissionRequested += CoreWebView2_PermissionRequested;
CoreWebView2.WebMessageReceived += CoreWebView2_WebMessageReceived;
logger.Info("Successfully initialized.");
}
else
@ -61,5 +67,47 @@ namespace SafeExamBrowser.Proctoring
logger.Info($"Denied access to {e.PermissionKind}.");
}
}
private void CoreWebView2_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e)
{
var message = e.TryGetWebMessageAsString();
logger.Debug($"Received web message '{message}'.");
switch (message)
{
case "credentials":
SendCredentials();
break;
}
}
private void SendCredentials()
{
var message = new JObject();
var credentials = new JObject();
if (settings.JitsiMeet.Enabled)
{
credentials.Add(new JProperty("domain", settings.JitsiMeet.ServerUrl));
credentials.Add(new JProperty("roomName", settings.JitsiMeet.RoomName));
credentials.Add(new JProperty("subject", settings.JitsiMeet.ShowMeetingName ? settings.JitsiMeet.Subject : ""));
credentials.Add(new JProperty("token", settings.JitsiMeet.Token));
}
else if (settings.Zoom.Enabled)
{
credentials.Add(new JProperty("apiKey", settings.Zoom.ApiKey));
credentials.Add(new JProperty("apiSecret", settings.Zoom.ApiSecret));
credentials.Add(new JProperty("meetingNumber", settings.Zoom.MeetingNumber));
credentials.Add(new JProperty("password", settings.Zoom.Password));
credentials.Add(new JProperty("signature", settings.Zoom.Signature));
credentials.Add(new JProperty("userName", settings.Zoom.UserName));
}
message.Add("credentials", credentials);
logger.Debug("Sending credentials to proctoring client.");
CoreWebView2.PostWebMessageAsJson(message.ToString());
}
}
}

View file

@ -169,7 +169,7 @@ namespace SafeExamBrowser.Proctoring
filePath = Path.Combine(appConfig.TemporaryDirectory, $"{Path.GetRandomFileName()}_index.html");
fileSystem.Save(content, filePath);
control = new ProctoringControl(logger.CloneFor(nameof(ProctoringControl)));
control = new ProctoringControl(logger.CloneFor(nameof(ProctoringControl)), settings);
control.CreationProperties = new CoreWebView2CreationProperties { UserDataFolder = appConfig.TemporaryDirectory };
control.EnsureCoreWebView2Async().ContinueWith(_ =>
{
@ -253,10 +253,6 @@ namespace SafeExamBrowser.Proctoring
html = html.Replace("%%_ALLOW_TILE_VIEW", settings.JitsiMeet.AllowTileView ? "tileview" : "");
html = html.Replace("'%_AUDIO_MUTED_%'", settings.JitsiMeet.AudioMuted && settings.WindowVisibility != WindowVisibility.Hidden ? "true" : "false");
html = html.Replace("'%_AUDIO_ONLY_%'", settings.JitsiMeet.AudioOnly ? "true" : "false");
html = html.Replace("%%_SUBJECT_%%", settings.JitsiMeet.ShowMeetingName ? settings.JitsiMeet.Subject : " ");
html = html.Replace("%%_DOMAIN_%%", settings.JitsiMeet.ServerUrl);
html = html.Replace("%%_ROOM_NAME_%%", settings.JitsiMeet.RoomName);
html = html.Replace("%%_TOKEN_%%", settings.JitsiMeet.Token);
html = html.Replace("'%_VIDEO_MUTED_%'", settings.JitsiMeet.VideoMuted && settings.WindowVisibility != WindowVisibility.Hidden ? "true" : "false");
}
else if (settings.Zoom.Enabled)
@ -264,13 +260,7 @@ namespace SafeExamBrowser.Proctoring
html = html.Replace("'%_ALLOW_CHAT_%'", settings.Zoom.AllowChat ? "true" : "false");
html = html.Replace("'%_ALLOW_CLOSED_CAPTIONS_%'", settings.Zoom.AllowClosedCaptions ? "true" : "false");
html = html.Replace("'%_ALLOW_RAISE_HAND_%'", settings.Zoom.AllowRaiseHand ? "true" : "false");
html = html.Replace("%%_API_KEY_%%", settings.Zoom.ApiKey);
html = html.Replace("%%_API_SECRET_%%", settings.Zoom.ApiSecret);
html = html.Replace("'%_AUDIO_MUTED_%'", settings.Zoom.AudioMuted && settings.WindowVisibility != WindowVisibility.Hidden ? "true" : "false");
html = html.Replace("%%_MEETING_NUMBER_%%", settings.Zoom.MeetingNumber);
html = html.Replace("%%_PASSWORD_%%", settings.Zoom.Password);
html = html.Replace("%%_SIGNATURE_%%", settings.Zoom.Signature);
html = html.Replace("%%_USER_NAME_%%", settings.Zoom.UserName);
html = html.Replace("'%_VIDEO_MUTED_%'", settings.Zoom.VideoMuted && settings.WindowVisibility != WindowVisibility.Hidden ? "true" : "false");
}

View file

@ -61,6 +61,9 @@
<Reference Include="Microsoft.Web.WebView2.Wpf, Version=1.0.864.35, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.864.35\lib\net45\Microsoft.Web.WebView2.Wpf.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" />
@ -116,7 +119,9 @@
<EmbeddedResource Include="Zoom\index.html" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="packages.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\Microsoft.Web.WebView2.1.0.864.35\build\Microsoft.Web.WebView2.targets" Condition="Exists('..\packages\Microsoft.Web.WebView2.1.0.864.35\build\Microsoft.Web.WebView2.targets')" />

View file

@ -14,19 +14,9 @@
<script src="https://source.zoom.us/zoom-meeting-1.9.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9/crypto-js.min.js"></script>
<script type="text/javascript">
const API_KEY = '%%_API_KEY_%%';
const API_SECRET = '%%_API_SECRET_%%';
const ATTENDEE = 0;
var audioJoin = 0;
var configuration = {
leaveUrl: 'doesnotexist',
meetingNumber: '%%_MEETING_NUMBER_%%',
passWord: '%%_PASSWORD_%%',
role: ATTENDEE,
userName: '%%_USER_NAME_%%'
};
var signature = '%%_SIGNATURE_%%';
var videoJoin = 0;
function initializeUserInterface(_) {
@ -69,81 +59,94 @@
}
requestAnimationFrame(initializeUserInterface);
};
if (!ZoomMtg.checkSystemRequirements()) {
alert('This system does not meet the necessary requirements for Zoom!');
}
ZoomMtg.setZoomJSLib('https://source.zoom.us/1.9.1/lib', '/av');
ZoomMtg.preLoadWasm();
ZoomMtg.prepareJssdk();
function startMeeting(credentials) {
var signature = credentials.signature;
if (!signature) {
signature = ZoomMtg.generateSignature({
meetingNumber: configuration.meetingNumber,
apiKey: API_KEY,
apiSecret: API_SECRET,
role: configuration.role,
if (!ZoomMtg.checkSystemRequirements()) {
alert('This system does not meet the necessary requirements for Zoom!');
}
ZoomMtg.setZoomJSLib('https://source.zoom.us/1.9.1/lib', '/av');
ZoomMtg.preLoadWasm();
ZoomMtg.prepareJssdk();
if (!signature) {
signature = ZoomMtg.generateSignature({
meetingNumber: credentials.meetingNumber,
apiKey: credentials.apiKey,
apiSecret: credentials.apiSecret,
role: ATTENDEE,
error: function (res) {
alert(`Failed to generate signature: ${JSON.stringify(res)}`);
}
});
}
ZoomMtg.init({
audioPanelAlwaysOpen: false,
disableCallOut: true,
disableInvite: true,
disableJoinAudio: false,
disableRecord: true,
disableReport: true,
disableVoIP: false,
leaveUrl: 'doesnotexist',
isLockBottom: true,
isShowJoiningErrorDialog: true,
isSupportAV: true,
isSupportBreakout: false,
isSupportChat: '%_ALLOW_CHAT_%',
isSupportCC: '%_ALLOW_CLOSED_CAPTIONS_%',
isSupportPolling: false,
isSupportQA: false,
isSupportNonverbal: false,
screenShare: false,
sharingMode: 'both',
showMeetingHeader: true,
showPureSharingContent: false,
videoDrag: true,
videoHeader: true,
meetingInfo: [
'topic',
'host',
'participant',
//'mn',
//'pwd',
//'telPwd',
//'invite',
//'dc'
],
error: function (res) {
alert(`Failed to generate signature: ${JSON.stringify(res)}`);
alert(`Failed to initialize meeting: ${JSON.stringify(res)}`);
},
success: function () {
ZoomMtg.join({
apiKey: credentials.apiKey,
meetingNumber: credentials.meetingNumber,
passWord: credentials.password,
signature: signature,
userName: credentials.userName,
error: function (res) {
alert(`Failed to join meeting: ${JSON.stringify(res)}`);
},
success: function (res) {
requestAnimationFrame(initializeUserInterface);
}
});
}
});
}
ZoomMtg.init({
audioPanelAlwaysOpen: false,
disableCallOut: true,
disableInvite: true,
disableJoinAudio: false,
disableRecord: true,
disableReport: true,
disableVoIP: false,
leaveUrl: configuration.leaveUrl,
isLockBottom: true,
isShowJoiningErrorDialog: true,
isSupportAV: true,
isSupportBreakout: false,
isSupportChat: '%_ALLOW_CHAT_%',
isSupportCC: '%_ALLOW_CLOSED_CAPTIONS_%',
isSupportPolling: false,
isSupportQA: false,
isSupportNonverbal: false,
screenShare: false,
sharingMode: 'both',
showMeetingHeader: true,
showPureSharingContent: false,
videoDrag: true,
videoHeader: true,
meetingInfo: [
'topic',
'host',
'participant',
//'mn',
//'pwd',
//'telPwd',
//'invite',
//'dc'
],
error: function (res) {
alert(`Failed to initialize meeting: ${JSON.stringify(res)}`);
},
success: function () {
ZoomMtg.join({
apiKey: API_KEY,
meetingNumber: configuration.meetingNumber,
passWord: configuration.passWord,
signature: signature,
userName: configuration.userName,
error: function (res) {
alert(`Failed to join meeting: ${JSON.stringify(res)}`);
},
success: function (res) {
requestAnimationFrame(initializeUserInterface);
}
});
function webMessageReceived(args) {
if ('credentials' in args.data) {
startMeeting(args.data.credentials);
}
});
}
window.chrome.webview.addEventListener('message', webMessageReceived);
window.chrome.webview.postMessage('credentials');
</script>
</body>
</html>

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Web.WebView2" version="1.0.864.35" targetFramework="net472" />
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net472" />
</packages>