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.

This commit is contained in:
Damian Büchel 2023-03-07 23:12:59 +01:00
parent 296f87727d
commit 0bb9f42a3a
8 changed files with 138 additions and 188 deletions

View file

@ -92,12 +92,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
case Keys.Proctoring.Zoom.AllowRaiseHand: case Keys.Proctoring.Zoom.AllowRaiseHand:
MapZoomAllowRaiseHands(settings, value); MapZoomAllowRaiseHands(settings, value);
break; break;
case Keys.Proctoring.Zoom.ApiKey:
MapZoomApiKey(settings, value);
break;
case Keys.Proctoring.Zoom.ApiSecret:
MapZoomApiSecret(settings, value);
break;
case Keys.Proctoring.Zoom.AudioMuted: case Keys.Proctoring.Zoom.AudioMuted:
MapZoomAudioMuted(settings, value); MapZoomAudioMuted(settings, value);
break; 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) private void MapZoomAudioMuted(AppSettings settings, object value)
{ {
if (value is bool muted) if (value is bool muted)

View file

@ -258,8 +258,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal const string AllowChat = "zoomFeatureFlagChat"; internal const string AllowChat = "zoomFeatureFlagChat";
internal const string AllowClosedCaptions = "zoomFeatureFlagCloseCaptions"; internal const string AllowClosedCaptions = "zoomFeatureFlagCloseCaptions";
internal const string AllowRaiseHand = "zoomFeatureFlagRaiseHand"; internal const string AllowRaiseHand = "zoomFeatureFlagRaiseHand";
internal const string ApiKey = "zoomApiKey";
internal const string ApiSecret = "zoomApiSecret";
internal const string AudioMuted = "zoomAudioMuted"; internal const string AudioMuted = "zoomAudioMuted";
internal const string Enabled = "zoomEnable"; internal const string Enabled = "zoomEnable";
internal const string MeetingNumber = "zoomRoom"; internal const string MeetingNumber = "zoomRoom";

View file

@ -96,10 +96,9 @@ namespace SafeExamBrowser.Proctoring
} }
else if (settings.Zoom.Enabled) 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("meetingNumber", settings.Zoom.MeetingNumber));
credentials.Add(new JProperty("password", settings.Zoom.Password)); 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("signature", settings.Zoom.Signature));
credentials.Add(new JProperty("userName", settings.Zoom.UserName)); credentials.Add(new JProperty("userName", settings.Zoom.UserName));
} }

View file

@ -103,8 +103,7 @@ namespace SafeExamBrowser.Proctoring
} }
else if (settings.Zoom.Enabled) else if (settings.Zoom.Enabled)
{ {
start = !string.IsNullOrWhiteSpace(settings.Zoom.ApiKey); start = !string.IsNullOrWhiteSpace(settings.Zoom.SdkKey) && !string.IsNullOrWhiteSpace(settings.Zoom.Signature);
start &= !string.IsNullOrWhiteSpace(settings.Zoom.ApiSecret) || !string.IsNullOrWhiteSpace(settings.Zoom.Signature);
start &= !string.IsNullOrWhiteSpace(settings.Zoom.MeetingNumber); start &= !string.IsNullOrWhiteSpace(settings.Zoom.MeetingNumber);
start &= !string.IsNullOrWhiteSpace(settings.Zoom.UserName); start &= !string.IsNullOrWhiteSpace(settings.Zoom.UserName);
} }
@ -168,9 +167,9 @@ namespace SafeExamBrowser.Proctoring
settings.JitsiMeet.ServerUrl = args.JitsiMeetServerUrl; settings.JitsiMeet.ServerUrl = args.JitsiMeetServerUrl;
settings.JitsiMeet.Token = args.JitsiMeetToken; settings.JitsiMeet.Token = args.JitsiMeetToken;
settings.Zoom.ApiKey = args.ZoomApiKey;
settings.Zoom.MeetingNumber = args.ZoomMeetingNumber; settings.Zoom.MeetingNumber = args.ZoomMeetingNumber;
settings.Zoom.Password = args.ZoomPassword; settings.Zoom.Password = args.ZoomPassword;
settings.Zoom.SdkKey = args.ZoomSdkKey;
settings.Zoom.Signature = args.ZoomSignature; settings.Zoom.Signature = args.ZoomSignature;
settings.Zoom.Subject = args.ZoomSubject; settings.Zoom.Subject = args.ZoomSubject;
settings.Zoom.UserName = args.ZoomUserName; settings.Zoom.UserName = args.ZoomUserName;
@ -272,8 +271,8 @@ namespace SafeExamBrowser.Proctoring
Thread.Sleep(2000); Thread.Sleep(2000);
window.Close(); window.Close();
control = default(ProctoringControl); control = default;
window = default(IProctoringWindow); window = default;
fileSystem.Delete(filePath); fileSystem.Delete(filePath);
logger.Info("Stopped proctoring."); logger.Info("Stopped proctoring.");

View file

@ -1,167 +1,148 @@
<html> <html>
<head> <head>
<meta charset="utf-8" /> <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.5.0/css/bootstrap.css" /> <link type="text/css" rel="stylesheet" href="https://source.zoom.us/2.10.1/css/react-select.css" />
<link type="text/css" rel="stylesheet" href="https://source.zoom.us/2.5.0/css/react-select.css" /> <meta charset="utf-8" />
</head> <meta http-equiv="origin-trial" content="" />
<body> </head>
<script src="https://source.zoom.us/2.5.0/lib/vendor/react.min.js"></script> <body>
<script src="https://source.zoom.us/2.5.0/lib/vendor/react-dom.min.js"></script> <script src="https://source.zoom.us/2.10.1/lib/vendor/react.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.10.1/lib/vendor/react-dom.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.10.1/lib/vendor/redux.min.js"></script>
<script src="https://source.zoom.us/2.5.0/lib/vendor/lodash.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/zoom-meeting-2.5.0.min.js"></script> <script src="https://source.zoom.us/2.10.1/lib/vendor/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9/crypto-js.min.js"></script> <script src="https://source.zoom.us/zoom-meeting-2.10.1.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
var audioJoin = 0; var audioJoin = 0;
var videoJoin = 0; var videoJoin = 0;
var join = 0; var join = 0;
function controlUserInterface(_) { function controlUserInterface(_) {
var appSignal = document.getElementById('app-signal'); var appSignal = document.getElementById('app-signal');
var audioButton = document.getElementsByClassName('join-audio-by-voip__join-btn')[0]; var audioButton = document.getElementsByClassName('join-audio-by-voip__join-btn')[0];
var audioContainer = document.getElementsByClassName('join-audio-container')[0]; var audioContainer = document.getElementsByClassName('join-audio-container')[0];
var joinButton = document.getElementsByClassName('joinWindowBtn')[0]; var joinButton = document.getElementsByClassName('joinWindowBtn')[0];
var leave = document.getElementsByClassName('footer__leave-btn-container')[0]; var leave = document.getElementsByClassName('footer__leave-btn-container')[0];
var more = document.getElementsByClassName('more-button')[0]; var more = document.getElementsByClassName('more-button')[0];
var participantControls = document.getElementsByClassName('participants-item__buttons')[0]; var participantControls = document.getElementsByClassName('participants-item__buttons')[0];
var raiseHandContainer = document.getElementsByClassName('participants-section-container__participants-footer')[0]; var raiseHandContainer = document.getElementsByClassName('participants-section-container__participants-footer')[0];
var videoButton = document.getElementsByClassName('send-video-container__btn')[0]; var videoButton = document.getElementsByClassName('send-video-container__btn')[0];
var videoContainer = document.getElementsByClassName('send-video-container')[0]; var videoContainer = document.getElementsByClassName('send-video-container')[0];
if (appSignal) { if (appSignal) {
appSignal.style.visibility = 'hidden'; appSignal.style.visibility = 'hidden';
} }
if (audioButton && !'%_AUDIO_MUTED_%' && audioJoin < 500) { if (audioButton && !'%_AUDIO_MUTED_%' && audioJoin < 500) {
audioButton.click(); audioButton.click();
audioJoin++; audioJoin++;
} }
if (audioContainer && !'%_AUDIO_MUTED_%') { if (audioContainer && !'%_AUDIO_MUTED_%') {
audioContainer.style.visibility = 'hidden'; audioContainer.style.visibility = 'hidden';
} }
if (joinButton && join < 500) { if (joinButton && join < 500) {
joinButton.click(); joinButton.click();
join++; join++;
} }
if (leave) { if (leave) {
leave.style.visibility = 'hidden'; leave.style.visibility = 'hidden';
} }
if (more) { if (more) {
more.style.visibility = 'hidden'; more.style.visibility = 'hidden';
} }
if (participantControls) { if (participantControls) {
participantControls.style.visibility = 'hidden'; participantControls.style.visibility = 'hidden';
} }
if (raiseHandContainer && !'%_ALLOW_RAISE_HAND_%') { if (raiseHandContainer && !'%_ALLOW_RAISE_HAND_%') {
raiseHandContainer.style.visibility = 'hidden'; raiseHandContainer.style.visibility = 'hidden';
} }
if (videoButton && !'%_VIDEO_MUTED_%' && videoJoin < 500) { if (videoButton && !'%_VIDEO_MUTED_%' && videoJoin < 500) {
videoButton.click(); videoButton.click();
videoJoin++; videoJoin++;
} }
if (videoContainer && !'%_VIDEO_MUTED_%') { if (videoContainer && !'%_VIDEO_MUTED_%') {
videoContainer.style.visibility = 'hidden'; videoContainer.style.visibility = 'hidden';
} }
requestAnimationFrame(controlUserInterface); requestAnimationFrame(controlUserInterface);
} }
function startMeeting(credentials) { function startMeeting(credentials) {
const ATTENDEE = 0; var error = function (res) {
var signature = credentials.signature; alert(`Failed to initialize meeting: ${JSON.stringify(res)}`);
};
if (!ZoomMtg.checkSystemRequirements()) { var success = function () {
alert('This system does not meet the necessary requirements for Zoom!'); requestAnimationFrame(controlUserInterface);
}
ZoomMtg.setZoomJSLib('https://source.zoom.us/2.5.0/lib', '/av'); ZoomMtg.join({
ZoomMtg.preLoadWasm(); meetingNumber: new Number(credentials.meetingNumber),
ZoomMtg.prepareJssdk(); 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) { var params = {
signature = ZoomMtg.generateSignature({ audioPanelAlwaysOpen: false,
meetingNumber: credentials.meetingNumber, disableCallOut: true,
apiKey: credentials.apiKey, disableInvite: true,
apiSecret: credentials.apiSecret, disableJoinAudio: false,
role: ATTENDEE, disableRecord: true,
error: function (res) { disableReport: true,
alert(`Failed to generate signature: ${JSON.stringify(res)}`); 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({ if (!ZoomMtg.checkSystemRequirements()) {
audioPanelAlwaysOpen: false, alert('This system does not meet the necessary requirements for Zoom!');
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);
ZoomMtg.join({ ZoomMtg.setZoomJSLib('https://source.zoom.us/2.10.1/lib', '/av');
apiKey: credentials.apiKey, ZoomMtg.preLoadWasm();
meetingNumber: credentials.meetingNumber, ZoomMtg.prepareWebSDK();
passWord: credentials.password, ZoomMtg.init(params);
signature: signature, }
userName: credentials.userName,
error: function (res) {
alert(`Failed to join meeting: ${JSON.stringify(res)}`);
}
});
}
});
}
function webMessageReceived(args) { function webMessageReceived(args) {
if ('credentials' in args.data) { if ('credentials' in args.data) {
startMeeting(args.data.credentials); startMeeting(args.data.credentials);
} }
} }
window.addEventListener('unload', () => ZoomMtg.leaveMeeting({})); window.addEventListener('unload', () => ZoomMtg.leaveMeeting({}));
window.chrome.webview.addEventListener('message', webMessageReceived); window.chrome.webview.addEventListener('message', webMessageReceived);
window.chrome.webview.postMessage('credentials'); window.chrome.webview.postMessage('credentials');
</script> </script>
</body> </body>
</html> </html>

View file

@ -16,9 +16,9 @@ namespace SafeExamBrowser.Server.Contracts.Events
public string JitsiMeetRoomName { get; set; } public string JitsiMeetRoomName { get; set; }
public string JitsiMeetServerUrl { get; set; } public string JitsiMeetServerUrl { get; set; }
public string JitsiMeetToken { get; set; } public string JitsiMeetToken { get; set; }
public string ZoomApiKey { get; set; }
public string ZoomMeetingNumber { get; set; } public string ZoomMeetingNumber { get; set; }
public string ZoomPassword { get; set; } public string ZoomPassword { get; set; }
public string ZoomSdkKey { get; set; }
public string ZoomSignature { get; set; } public string ZoomSignature { get; set; }
public string ZoomSubject { get; set; } public string ZoomSubject { get; set; }
public string ZoomUserName { get; set; } public string ZoomUserName { get; set; }

View file

@ -298,9 +298,9 @@ namespace SafeExamBrowser.Server
attributes.Instruction.JitsiMeetToken = attributesJson["jitsiMeetToken"].Value<string>(); attributes.Instruction.JitsiMeetToken = attributesJson["jitsiMeetToken"].Value<string>();
break; break;
case "ZOOM": case "ZOOM":
attributes.Instruction.ZoomApiKey = attributesJson["zoomAPIKey"].Value<string>();
attributes.Instruction.ZoomMeetingNumber = attributesJson["zoomRoom"].Value<string>(); attributes.Instruction.ZoomMeetingNumber = attributesJson["zoomRoom"].Value<string>();
attributes.Instruction.ZoomPassword = attributesJson["zoomMeetingKey"].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.ZoomSignature = attributesJson["zoomToken"].Value<string>();
attributes.Instruction.ZoomSubject = attributesJson["zoomSubject"].Value<string>(); attributes.Instruction.ZoomSubject = attributesJson["zoomSubject"].Value<string>();
attributes.Instruction.ZoomUserName = attributesJson["zoomUserName"].Value<string>(); attributes.Instruction.ZoomUserName = attributesJson["zoomUserName"].Value<string>();

View file

@ -31,16 +31,6 @@ namespace SafeExamBrowser.Settings.Proctoring
/// </summary> /// </summary>
public bool AllowRaiseHand { get; set; } 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> /// <summary>
/// Determines whether the audio starts muted. /// Determines whether the audio starts muted.
/// </summary> /// </summary>
@ -71,6 +61,11 @@ namespace SafeExamBrowser.Settings.Proctoring
/// </summary> /// </summary>
public bool ReceiveVideo { get; set; } public bool ReceiveVideo { get; set; }
/// <summary>
/// The SDK key to be used for authentication.
/// </summary>
public string SdkKey { get; set; }
/// <summary> /// <summary>
/// Determines whether the audio stream of the user will be sent to the server. /// Determines whether the audio stream of the user will be sent to the server.
/// </summary> /// </summary>