From da39fb1f5900cca3bf0e61662a59ffa4e190b402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20B=C3=BCchel?= Date: Sat, 19 Jun 2021 21:04:13 +0200 Subject: [PATCH] SEBWIN-475: Implemented basic Zoom proctoring integration. --- .../DataMapping/ProctoringDataMapper.cs | 11 +++ .../ConfigurationData/DataValues.cs | 3 + .../ConfigurationData/Keys.cs | 1 + .../ProctoringController.cs | 19 +++- SafeExamBrowser.Proctoring/Zoom/index.html | 96 +++++++++++++------ .../Events/ProctoringInstructionEventArgs.cs | 1 + SafeExamBrowser.Server/Parser.cs | 1 + .../Proctoring/ZoomSettings.cs | 20 ++++ 8 files changed, 120 insertions(+), 32 deletions(-) diff --git a/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/ProctoringDataMapper.cs b/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/ProctoringDataMapper.cs index b86cd786..6e600349 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/ProctoringDataMapper.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/ProctoringDataMapper.cs @@ -92,6 +92,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping case Keys.Proctoring.Zoom.Signature: MapZoomSignature(settings, value); break; + case Keys.Proctoring.Zoom.Subject: + MapZoomSubject(settings, value); + break; case Keys.Proctoring.Zoom.UserName: MapZoomUserName(settings, value); break; @@ -317,6 +320,14 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping } } + private void MapZoomSubject(AppSettings settings, object value) + { + if (value is string subject) + { + settings.Proctoring.Zoom.Subject = subject; + } + } + private void MapZoomUserName(AppSettings settings, object value) { if (value is string name) diff --git a/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs b/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs index a8ddf0cd..eaca90ce 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs @@ -233,7 +233,10 @@ namespace SafeExamBrowser.Configuration.ConfigurationData settings.Proctoring.JitsiMeet.VideoMuted = false; settings.Proctoring.ShowTaskbarNotification = true; settings.Proctoring.WindowVisibility = WindowVisibility.Hidden; + settings.Proctoring.Zoom.AllowChat = false; settings.Proctoring.Zoom.Enabled = false; + settings.Proctoring.Zoom.ReceiveAudio = false; + settings.Proctoring.Zoom.ReceiveVideo = false; settings.Security.AllowApplicationLogAccess = false; settings.Security.AllowTermination = true; diff --git a/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs b/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs index 2cfb0b87..e294e76d 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs @@ -255,6 +255,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData internal const string Enabled = "zoomEnable"; internal const string MeetingNumber = "zoomRoom"; internal const string Signature = "zoomToken"; + internal const string Subject = "zoomSubject"; internal const string UserName = "zoomUserInfoDisplayName"; } } diff --git a/SafeExamBrowser.Proctoring/ProctoringController.cs b/SafeExamBrowser.Proctoring/ProctoringController.cs index 2a5f24f0..86d331f2 100644 --- a/SafeExamBrowser.Proctoring/ProctoringController.cs +++ b/SafeExamBrowser.Proctoring/ProctoringController.cs @@ -126,6 +126,7 @@ namespace SafeExamBrowser.Proctoring settings.Zoom.MeetingNumber = args.ZoomMeetingNumber; settings.Zoom.Password = args.ZoomPassword; settings.Zoom.Signature = args.ZoomSignature; + settings.Zoom.Subject = args.ZoomSubject; settings.Zoom.UserName = args.ZoomUserName; StopProctoring(); @@ -140,6 +141,10 @@ namespace SafeExamBrowser.Proctoring settings.JitsiMeet.ReceiveAudio = receiveAudio; settings.JitsiMeet.ReceiveVideo = receiveVideo; + settings.Zoom.AllowChat = allowChat; + settings.Zoom.ReceiveAudio = receiveAudio; + settings.Zoom.ReceiveVideo = receiveVideo; + if (allowChat || receiveVideo) { settings.WindowVisibility = WindowVisibility.AllowToHide; @@ -149,7 +154,6 @@ namespace SafeExamBrowser.Proctoring settings.WindowVisibility = windowVisibility; } - // TODO: This is apparently not necessary for Zoom, there we can enable / disable the options via API call! StopProctoring(); StartProctoring(); } @@ -176,7 +180,7 @@ namespace SafeExamBrowser.Proctoring }); window = uiFactory.CreateProctoringWindow(control); - window.SetTitle(settings.JitsiMeet.Enabled ? settings.JitsiMeet.Subject : settings.Zoom.UserName); + window.SetTitle(settings.JitsiMeet.Enabled ? settings.JitsiMeet.Subject : settings.Zoom.Subject); window.Show(); if (settings.WindowVisibility == WindowVisibility.AllowToShow || settings.WindowVisibility == WindowVisibility.Hidden) @@ -203,7 +207,15 @@ namespace SafeExamBrowser.Proctoring { control.Dispatcher.Invoke(() => { - control.ExecuteScriptAsync("api.executeCommand('hangup'); api.dispose();"); + if (settings.JitsiMeet.Enabled) + { + control.ExecuteScriptAsync("api.executeCommand('hangup'); api.dispose();"); + } + else if (settings.Zoom.Enabled) + { + control.ExecuteScriptAsync("ZoomMtg.leaveMeeting({});"); + } + window.Close(); control = default(ProctoringControl); window = default(IProctoringWindow); @@ -242,6 +254,7 @@ namespace SafeExamBrowser.Proctoring } else if (settings.Zoom.Enabled) { + html = html.Replace("'%_ALLOW_CHAT_%'", settings.Zoom.AllowChat ? "true" : "false"); html = html.Replace("%%_API_KEY_%%", settings.Zoom.ApiKey); html = html.Replace("%%_API_SECRET_%%", settings.Zoom.ApiSecret); html = html.Replace("%%_MEETING_NUMBER_%%", settings.Zoom.MeetingNumber); diff --git a/SafeExamBrowser.Proctoring/Zoom/index.html b/SafeExamBrowser.Proctoring/Zoom/index.html index 7e49eaee..c6b429c7 100644 --- a/SafeExamBrowser.Proctoring/Zoom/index.html +++ b/SafeExamBrowser.Proctoring/Zoom/index.html @@ -18,6 +18,7 @@ const API_SECRET = '%%_API_SECRET_%%'; const ATTENDEE = 0; + var audioJoin = 0; var configuration = { leaveUrl: 'doesnotexist', meetingNumber: '%%_MEETING_NUMBER_%%', @@ -26,6 +27,39 @@ userName: '%%_USER_NAME_%%' }; var signature = '%%_SIGNATURE_%%'; + var videoJoin = 0; + + function initializeUserInterface(_) { + var audioButton = document.getElementsByClassName('join-audio-by-voip__join-btn')[0]; + var audioContainer = document.getElementsByClassName('join-audio-container')[0]; + var leave = document.getElementsByClassName('footer__leave-btn-container')[0]; + var videoButton = document.getElementsByClassName('send-video-container__btn')[0]; + var videoContainer = document.getElementsByClassName('send-video-container')[0]; + + if (audioButton && audioJoin < 100) { + audioButton.click(); + audioJoin++; + } + + if (audioContainer) { + audioContainer.style.visibility = "hidden"; + } + + if (leave) { + leave.style.visibility = "hidden"; + } + + if (videoButton && videoJoin < 100) { + videoButton.click(); + videoJoin++; + } + + if (videoContainer) { + videoContainer.style.visibility = "hidden"; + } + + requestAnimationFrame(initializeUserInterface); + }; if (!ZoomMtg.checkSystemRequirements()) { alert('This system does not meet the necessary requirements for Zoom!'); @@ -42,46 +76,47 @@ apiSecret: API_SECRET, role: configuration.role, error: function (res) { - alert(`Failed to generate signature: ${JSON.stringify(res)}`) + alert(`Failed to generate signature: ${JSON.stringify(res)}`); } }); } ZoomMtg.init({ - leaveUrl: configuration.leaveUrl, - showMeetingHeader: true, - disableInvite: false, - disableCallOut: false, - disableRecord: false, + audioPanelAlwaysOpen: false, + disableCallOut: true, + disableInvite: true, disableJoinAudio: false, - audioPanelAlwaysOpen: true, - showPureSharingContent: false, - isSupportAV: true, - isSupportChat: true, - isSupportQA: true, - isSupportCC: true, - screenShare: true, - videoDrag: true, - sharingMode: 'both', - videoHeader: true, + disableRecord: true, + disableReport: true, + disableVoIP: false, + leaveUrl: configuration.leaveUrl, isLockBottom: true, - isSupportNonverbal: true, isShowJoiningErrorDialog: true, - inviteUrlFormat: '', + isSupportAV: true, + isSupportBreakout: false, + isSupportChat: '%_ALLOW_CHAT_%', + isSupportCC: false, + isSupportPolling: false, + isSupportQA: false, + isSupportNonverbal: false, + screenShare: false, + sharingMode: 'both', + showMeetingHeader: true, + showPureSharingContent: false, + videoDrag: true, + videoHeader: true, meetingInfo: [ 'topic', 'host', - 'mn', - 'pwd', - 'telPwd', - 'invite', 'participant', - 'dc' + //'mn', + //'pwd', + //'telPwd', + //'invite', + //'dc' ], - disableVoIP: false, - disableReport: false, error: function (res) { - alert(`Failed to initialize meeting: ${JSON.stringify(res)}`) + alert(`Failed to initialize meeting: ${JSON.stringify(res)}`); }, success: function () { ZoomMtg.join({ @@ -91,11 +126,14 @@ signature: signature, userName: configuration.userName, error: function (res) { - alert(`Failed to join meeting: ${JSON.stringify(res)}`) + alert(`Failed to join meeting: ${JSON.stringify(res)}`); + }, + success: function (res) { + requestAnimationFrame(initializeUserInterface); } - }) + }); } - }) + }); \ No newline at end of file diff --git a/SafeExamBrowser.Server.Contracts/Events/ProctoringInstructionEventArgs.cs b/SafeExamBrowser.Server.Contracts/Events/ProctoringInstructionEventArgs.cs index 9b492089..5e1c41eb 100644 --- a/SafeExamBrowser.Server.Contracts/Events/ProctoringInstructionEventArgs.cs +++ b/SafeExamBrowser.Server.Contracts/Events/ProctoringInstructionEventArgs.cs @@ -20,6 +20,7 @@ namespace SafeExamBrowser.Server.Contracts.Events public string ZoomMeetingNumber { get; set; } public string ZoomPassword { get; set; } public string ZoomSignature { get; set; } + public string ZoomSubject { get; set; } public string ZoomUserName { get; set; } } } diff --git a/SafeExamBrowser.Server/Parser.cs b/SafeExamBrowser.Server/Parser.cs index 7c7369a0..263392cc 100644 --- a/SafeExamBrowser.Server/Parser.cs +++ b/SafeExamBrowser.Server/Parser.cs @@ -206,6 +206,7 @@ namespace SafeExamBrowser.Server attributes.Instruction.ZoomMeetingNumber = attributesJson["zoomRoom"].Value(); attributes.Instruction.ZoomPassword = attributesJson["zoomMeetingKey"].Value(); attributes.Instruction.ZoomSignature = attributesJson["zoomToken"].Value(); + attributes.Instruction.ZoomSubject = attributesJson["zoomSubject"].Value(); attributes.Instruction.ZoomUserName = attributesJson["zoomUserName"].Value(); break; } diff --git a/SafeExamBrowser.Settings/Proctoring/ZoomSettings.cs b/SafeExamBrowser.Settings/Proctoring/ZoomSettings.cs index 4542a771..70dae978 100644 --- a/SafeExamBrowser.Settings/Proctoring/ZoomSettings.cs +++ b/SafeExamBrowser.Settings/Proctoring/ZoomSettings.cs @@ -16,6 +16,11 @@ namespace SafeExamBrowser.Settings.Proctoring [Serializable] public class ZoomSettings { + /// + /// Determines whether the user can use the chat. + /// + public bool AllowChat { get; set; } + /// /// The API key to be used for authentication. /// @@ -41,11 +46,26 @@ namespace SafeExamBrowser.Settings.Proctoring /// public string Password { get; set; } + /// + /// Determines whether the user may receive the video stream of other meeting participants. + /// + public bool ReceiveAudio { get; set; } + + /// + /// Determines whether the user may receive the audio stream of other meeting participants. + /// + public bool ReceiveVideo { get; set; } + /// /// The signature to be used for authentication. /// public string Signature { get; set; } + /// + /// The subject of the meeting. + /// + public string Subject { get; set; } + /// /// The user name to be used for the meeting. ///