From 0bb9f42a3a20b31142af10eeeae2142a5135aab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20B=C3=BCchel?= <damian.buechel@let.ethz.ch> Date: Tue, 7 Mar 2023 23:12:59 +0100 Subject: [PATCH] SEBWIN-623, SEBWIN-628, SEBWIN-641, #521: Updated Zoom WebSDK to version 2.10.1 and changed authentication to use SDK key and JWT token. --- .../DataMapping/ProctoringDataMapper.cs | 22 -- .../ConfigurationData/Keys.cs | 2 - .../ProctoringControl.cs | 3 +- .../ProctoringController.cs | 9 +- SafeExamBrowser.Proctoring/Zoom/index.html | 271 ++++++++---------- .../Events/ProctoringInstructionEventArgs.cs | 2 +- SafeExamBrowser.Server/Parser.cs | 2 +- .../Proctoring/ZoomSettings.cs | 15 +- 8 files changed, 138 insertions(+), 188 deletions(-) diff --git a/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/ProctoringDataMapper.cs b/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/ProctoringDataMapper.cs index cf4a2f38..a55f36d6 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/ProctoringDataMapper.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/ProctoringDataMapper.cs @@ -92,12 +92,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping case Keys.Proctoring.Zoom.AllowRaiseHand: MapZoomAllowRaiseHands(settings, value); break; - case Keys.Proctoring.Zoom.ApiKey: - MapZoomApiKey(settings, value); - break; - case Keys.Proctoring.Zoom.ApiSecret: - MapZoomApiSecret(settings, value); - break; case Keys.Proctoring.Zoom.AudioMuted: MapZoomAudioMuted(settings, value); break; @@ -353,22 +347,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping } } - private void MapZoomApiKey(AppSettings settings, object value) - { - if (value is string key) - { - settings.Proctoring.Zoom.ApiKey = key; - } - } - - private void MapZoomApiSecret(AppSettings settings, object value) - { - if (value is string secret) - { - settings.Proctoring.Zoom.ApiSecret = secret; - } - } - private void MapZoomAudioMuted(AppSettings settings, object value) { if (value is bool muted) diff --git a/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs b/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs index e14897dc..a6cd5795 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs @@ -258,8 +258,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData internal const string AllowChat = "zoomFeatureFlagChat"; internal const string AllowClosedCaptions = "zoomFeatureFlagCloseCaptions"; internal const string AllowRaiseHand = "zoomFeatureFlagRaiseHand"; - internal const string ApiKey = "zoomApiKey"; - internal const string ApiSecret = "zoomApiSecret"; internal const string AudioMuted = "zoomAudioMuted"; internal const string Enabled = "zoomEnable"; internal const string MeetingNumber = "zoomRoom"; diff --git a/SafeExamBrowser.Proctoring/ProctoringControl.cs b/SafeExamBrowser.Proctoring/ProctoringControl.cs index 2d04669c..568cd95f 100644 --- a/SafeExamBrowser.Proctoring/ProctoringControl.cs +++ b/SafeExamBrowser.Proctoring/ProctoringControl.cs @@ -96,10 +96,9 @@ namespace SafeExamBrowser.Proctoring } 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("sdkKey", settings.Zoom.SdkKey)); credentials.Add(new JProperty("signature", settings.Zoom.Signature)); credentials.Add(new JProperty("userName", settings.Zoom.UserName)); } diff --git a/SafeExamBrowser.Proctoring/ProctoringController.cs b/SafeExamBrowser.Proctoring/ProctoringController.cs index e6313ead..7c1e936b 100644 --- a/SafeExamBrowser.Proctoring/ProctoringController.cs +++ b/SafeExamBrowser.Proctoring/ProctoringController.cs @@ -103,8 +103,7 @@ namespace SafeExamBrowser.Proctoring } else if (settings.Zoom.Enabled) { - start = !string.IsNullOrWhiteSpace(settings.Zoom.ApiKey); - start &= !string.IsNullOrWhiteSpace(settings.Zoom.ApiSecret) || !string.IsNullOrWhiteSpace(settings.Zoom.Signature); + start = !string.IsNullOrWhiteSpace(settings.Zoom.SdkKey) && !string.IsNullOrWhiteSpace(settings.Zoom.Signature); start &= !string.IsNullOrWhiteSpace(settings.Zoom.MeetingNumber); start &= !string.IsNullOrWhiteSpace(settings.Zoom.UserName); } @@ -168,9 +167,9 @@ namespace SafeExamBrowser.Proctoring settings.JitsiMeet.ServerUrl = args.JitsiMeetServerUrl; settings.JitsiMeet.Token = args.JitsiMeetToken; - settings.Zoom.ApiKey = args.ZoomApiKey; settings.Zoom.MeetingNumber = args.ZoomMeetingNumber; settings.Zoom.Password = args.ZoomPassword; + settings.Zoom.SdkKey = args.ZoomSdkKey; settings.Zoom.Signature = args.ZoomSignature; settings.Zoom.Subject = args.ZoomSubject; settings.Zoom.UserName = args.ZoomUserName; @@ -272,8 +271,8 @@ namespace SafeExamBrowser.Proctoring Thread.Sleep(2000); window.Close(); - control = default(ProctoringControl); - window = default(IProctoringWindow); + control = default; + window = default; fileSystem.Delete(filePath); logger.Info("Stopped proctoring."); diff --git a/SafeExamBrowser.Proctoring/Zoom/index.html b/SafeExamBrowser.Proctoring/Zoom/index.html index 515aef40..85f7169d 100644 --- a/SafeExamBrowser.Proctoring/Zoom/index.html +++ b/SafeExamBrowser.Proctoring/Zoom/index.html @@ -1,167 +1,148 @@ <html> - <head> - <meta charset="utf-8" /> - <link type="text/css" rel="stylesheet" href="https://source.zoom.us/2.5.0/css/bootstrap.css" /> - <link type="text/css" rel="stylesheet" href="https://source.zoom.us/2.5.0/css/react-select.css" /> - </head> - <body> - <script src="https://source.zoom.us/2.5.0/lib/vendor/react.min.js"></script> - <script src="https://source.zoom.us/2.5.0/lib/vendor/react-dom.min.js"></script> - <script src="https://source.zoom.us/2.5.0/lib/vendor/redux.min.js"></script> - <script src="https://source.zoom.us/2.5.0/lib/vendor/redux-thunk.min.js"></script> - <script src="https://source.zoom.us/2.5.0/lib/vendor/lodash.min.js"></script> - <script src="https://source.zoom.us/zoom-meeting-2.5.0.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"> - var audioJoin = 0; - var videoJoin = 0; - var join = 0; + <head> + <link type="text/css" rel="stylesheet" href="https://source.zoom.us/2.10.1/css/bootstrap.css" /> + <link type="text/css" rel="stylesheet" href="https://source.zoom.us/2.10.1/css/react-select.css" /> + <meta charset="utf-8" /> + <meta http-equiv="origin-trial" content="" /> + </head> + <body> + <script src="https://source.zoom.us/2.10.1/lib/vendor/react.min.js"></script> + <script src="https://source.zoom.us/2.10.1/lib/vendor/react-dom.min.js"></script> + <script src="https://source.zoom.us/2.10.1/lib/vendor/redux.min.js"></script> + <script src="https://source.zoom.us/2.10.1/lib/vendor/redux-thunk.min.js"></script> + <script src="https://source.zoom.us/2.10.1/lib/vendor/lodash.min.js"></script> + <script src="https://source.zoom.us/zoom-meeting-2.10.1.min.js"></script> + <script type="text/javascript"> + var audioJoin = 0; + var videoJoin = 0; + var join = 0; - function controlUserInterface(_) { - var appSignal = document.getElementById('app-signal'); - var audioButton = document.getElementsByClassName('join-audio-by-voip__join-btn')[0]; - var audioContainer = document.getElementsByClassName('join-audio-container')[0]; - var joinButton = document.getElementsByClassName('joinWindowBtn')[0]; - var leave = document.getElementsByClassName('footer__leave-btn-container')[0]; - var more = document.getElementsByClassName('more-button')[0]; - var participantControls = document.getElementsByClassName('participants-item__buttons')[0]; - var raiseHandContainer = document.getElementsByClassName('participants-section-container__participants-footer')[0]; - var videoButton = document.getElementsByClassName('send-video-container__btn')[0]; - var videoContainer = document.getElementsByClassName('send-video-container')[0]; + function controlUserInterface(_) { + var appSignal = document.getElementById('app-signal'); + var audioButton = document.getElementsByClassName('join-audio-by-voip__join-btn')[0]; + var audioContainer = document.getElementsByClassName('join-audio-container')[0]; + var joinButton = document.getElementsByClassName('joinWindowBtn')[0]; + var leave = document.getElementsByClassName('footer__leave-btn-container')[0]; + var more = document.getElementsByClassName('more-button')[0]; + var participantControls = document.getElementsByClassName('participants-item__buttons')[0]; + var raiseHandContainer = document.getElementsByClassName('participants-section-container__participants-footer')[0]; + var videoButton = document.getElementsByClassName('send-video-container__btn')[0]; + var videoContainer = document.getElementsByClassName('send-video-container')[0]; - if (appSignal) { - appSignal.style.visibility = 'hidden'; - } + if (appSignal) { + appSignal.style.visibility = 'hidden'; + } - if (audioButton && !'%_AUDIO_MUTED_%' && audioJoin < 500) { - audioButton.click(); - audioJoin++; - } + if (audioButton && !'%_AUDIO_MUTED_%' && audioJoin < 500) { + audioButton.click(); + audioJoin++; + } - if (audioContainer && !'%_AUDIO_MUTED_%') { - audioContainer.style.visibility = 'hidden'; - } + if (audioContainer && !'%_AUDIO_MUTED_%') { + audioContainer.style.visibility = 'hidden'; + } - if (joinButton && join < 500) { - joinButton.click(); - join++; - } + if (joinButton && join < 500) { + joinButton.click(); + join++; + } - if (leave) { - leave.style.visibility = 'hidden'; - } + if (leave) { + leave.style.visibility = 'hidden'; + } - if (more) { - more.style.visibility = 'hidden'; - } + if (more) { + more.style.visibility = 'hidden'; + } - if (participantControls) { - participantControls.style.visibility = 'hidden'; - } + if (participantControls) { + participantControls.style.visibility = 'hidden'; + } - if (raiseHandContainer && !'%_ALLOW_RAISE_HAND_%') { - raiseHandContainer.style.visibility = 'hidden'; - } + if (raiseHandContainer && !'%_ALLOW_RAISE_HAND_%') { + raiseHandContainer.style.visibility = 'hidden'; + } - if (videoButton && !'%_VIDEO_MUTED_%' && videoJoin < 500) { - videoButton.click(); - videoJoin++; - } + if (videoButton && !'%_VIDEO_MUTED_%' && videoJoin < 500) { + videoButton.click(); + videoJoin++; + } - if (videoContainer && !'%_VIDEO_MUTED_%') { - videoContainer.style.visibility = 'hidden'; - } + if (videoContainer && !'%_VIDEO_MUTED_%') { + videoContainer.style.visibility = 'hidden'; + } - requestAnimationFrame(controlUserInterface); - } + requestAnimationFrame(controlUserInterface); + } - function startMeeting(credentials) { - const ATTENDEE = 0; - var signature = credentials.signature; + function startMeeting(credentials) { + var error = function (res) { + alert(`Failed to initialize meeting: ${JSON.stringify(res)}`); + }; - if (!ZoomMtg.checkSystemRequirements()) { - alert('This system does not meet the necessary requirements for Zoom!'); - } + var success = function () { + requestAnimationFrame(controlUserInterface); - ZoomMtg.setZoomJSLib('https://source.zoom.us/2.5.0/lib', '/av'); - ZoomMtg.preLoadWasm(); - ZoomMtg.prepareJssdk(); + ZoomMtg.join({ + meetingNumber: new Number(credentials.meetingNumber), + passWord: credentials.password, + sdkKey: credentials.sdkKey, + signature: credentials.signature, + userName: credentials.userName, + error: function (res) { + alert(`Failed to join meeting: ${JSON.stringify(res)}`); + } + }); + }; - 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)}`); - } - }); - } + var params = { + 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'], + error: error, + success: success + }; - 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 initialize meeting: ${JSON.stringify(res)}`); - }, - success: function () { - requestAnimationFrame(controlUserInterface); + if (!ZoomMtg.checkSystemRequirements()) { + alert('This system does not meet the necessary requirements for Zoom!'); + } - 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)}`); - } - }); - } - }); - } + ZoomMtg.setZoomJSLib('https://source.zoom.us/2.10.1/lib', '/av'); + ZoomMtg.preLoadWasm(); + ZoomMtg.prepareWebSDK(); + ZoomMtg.init(params); + } - function webMessageReceived(args) { - if ('credentials' in args.data) { - startMeeting(args.data.credentials); - } - } + function webMessageReceived(args) { + if ('credentials' in args.data) { + startMeeting(args.data.credentials); + } + } - window.addEventListener('unload', () => ZoomMtg.leaveMeeting({})); - window.chrome.webview.addEventListener('message', webMessageReceived); - window.chrome.webview.postMessage('credentials'); - </script> - </body> + window.addEventListener('unload', () => ZoomMtg.leaveMeeting({})); + window.chrome.webview.addEventListener('message', webMessageReceived); + window.chrome.webview.postMessage('credentials'); + </script> + </body> </html> \ No newline at end of file diff --git a/SafeExamBrowser.Server.Contracts/Events/ProctoringInstructionEventArgs.cs b/SafeExamBrowser.Server.Contracts/Events/ProctoringInstructionEventArgs.cs index 2cbfd313..bae66b4b 100644 --- a/SafeExamBrowser.Server.Contracts/Events/ProctoringInstructionEventArgs.cs +++ b/SafeExamBrowser.Server.Contracts/Events/ProctoringInstructionEventArgs.cs @@ -16,9 +16,9 @@ namespace SafeExamBrowser.Server.Contracts.Events public string JitsiMeetRoomName { get; set; } public string JitsiMeetServerUrl { get; set; } public string JitsiMeetToken { get; set; } - public string ZoomApiKey { get; set; } public string ZoomMeetingNumber { get; set; } public string ZoomPassword { get; set; } + public string ZoomSdkKey { 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 3ed53020..3c6d7cfd 100644 --- a/SafeExamBrowser.Server/Parser.cs +++ b/SafeExamBrowser.Server/Parser.cs @@ -298,9 +298,9 @@ namespace SafeExamBrowser.Server attributes.Instruction.JitsiMeetToken = attributesJson["jitsiMeetToken"].Value<string>(); break; case "ZOOM": - attributes.Instruction.ZoomApiKey = attributesJson["zoomAPIKey"].Value<string>(); attributes.Instruction.ZoomMeetingNumber = attributesJson["zoomRoom"].Value<string>(); attributes.Instruction.ZoomPassword = attributesJson["zoomMeetingKey"].Value<string>(); + attributes.Instruction.ZoomSdkKey = attributesJson["zoomAPIKey"].Value<string>(); attributes.Instruction.ZoomSignature = attributesJson["zoomToken"].Value<string>(); attributes.Instruction.ZoomSubject = attributesJson["zoomSubject"].Value<string>(); attributes.Instruction.ZoomUserName = attributesJson["zoomUserName"].Value<string>(); diff --git a/SafeExamBrowser.Settings/Proctoring/ZoomSettings.cs b/SafeExamBrowser.Settings/Proctoring/ZoomSettings.cs index bec6c3d3..fca61ea5 100644 --- a/SafeExamBrowser.Settings/Proctoring/ZoomSettings.cs +++ b/SafeExamBrowser.Settings/Proctoring/ZoomSettings.cs @@ -31,16 +31,6 @@ namespace SafeExamBrowser.Settings.Proctoring /// </summary> public bool AllowRaiseHand { get; set; } - /// <summary> - /// The API key to be used for authentication. - /// </summary> - public string ApiKey { get; set; } - - /// <summary> - /// The API secret to be used for authentication. - /// </summary> - public string ApiSecret { get; set; } - /// <summary> /// Determines whether the audio starts muted. /// </summary> @@ -71,6 +61,11 @@ namespace SafeExamBrowser.Settings.Proctoring /// </summary> public bool ReceiveVideo { get; set; } + /// <summary> + /// The SDK key to be used for authentication. + /// </summary> + public string SdkKey { get; set; } + /// <summary> /// Determines whether the audio stream of the user will be sent to the server. /// </summary>