SEBWIN-475: Implemented basic Zoom proctoring integration.

This commit is contained in:
Damian Büchel 2021-06-19 21:04:13 +02:00
parent 7ad1d6ae5d
commit da39fb1f59
8 changed files with 120 additions and 32 deletions

View file

@ -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)

View file

@ -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;

View file

@ -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";
}
}

View file

@ -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);

View file

@ -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);
}
})
});
}
})
});
</script>
</body>
</html>

View file

@ -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; }
}
}

View file

@ -206,6 +206,7 @@ namespace SafeExamBrowser.Server
attributes.Instruction.ZoomMeetingNumber = attributesJson["zoomRoom"].Value<string>();
attributes.Instruction.ZoomPassword = attributesJson["zoomMeetingKey"].Value<string>();
attributes.Instruction.ZoomSignature = attributesJson["zoomToken"].Value<string>();
attributes.Instruction.ZoomSubject = attributesJson["zoomSubject"].Value<string>();
attributes.Instruction.ZoomUserName = attributesJson["zoomUserName"].Value<string>();
break;
}

View file

@ -16,6 +16,11 @@ namespace SafeExamBrowser.Settings.Proctoring
[Serializable]
public class ZoomSettings
{
/// <summary>
/// Determines whether the user can use the chat.
/// </summary>
public bool AllowChat { get; set; }
/// <summary>
/// The API key to be used for authentication.
/// </summary>
@ -41,11 +46,26 @@ namespace SafeExamBrowser.Settings.Proctoring
/// </summary>
public string Password { get; set; }
/// <summary>
/// Determines whether the user may receive the video stream of other meeting participants.
/// </summary>
public bool ReceiveAudio { get; set; }
/// <summary>
/// Determines whether the user may receive the audio stream of other meeting participants.
/// </summary>
public bool ReceiveVideo { get; set; }
/// <summary>
/// The signature to be used for authentication.
/// </summary>
public string Signature { get; set; }
/// <summary>
/// The subject of the meeting.
/// </summary>
public string Subject { get; set; }
/// <summary>
/// The user name to be used for the meeting.
/// </summary>