Skip to content

Commit

Permalink
Merge pull request #109 from talview/add_additional_secure_browser_co…
Browse files Browse the repository at this point in the history
…nfigurations

Add additional secure browser configurations
  • Loading branch information
rohansharmasitoula authored Nov 5, 2024
2 parents 17686e0 + a71a3c3 commit 1e0347b
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 21 deletions.
76 changes: 74 additions & 2 deletions classes/local/api/tracker.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,66 @@ public static function storeFallbackDetails($attempt_no, $proview_url, $proctor_
]);
return $response;
}
private static function redirect_to_wrapper($proctoring_payload, $quiz, $quizaccess_proctor_setting)
{
$wrapper_response = self::create_sb_wrapper($proctoring_payload, $quiz, $quizaccess_proctor_setting);
// redirect($wrapper_response->signed_short_url);
echo "<script> window.location='$wrapper_response->signed_url';</script>";
return;
}

private static function create_sb_wrapper($proctoring_payload, $quiz, $quizaccess_proctor_setting)
{
global $PAGE;
$curl = new \curl();
$api_base_url = trim(get_config('quizaccess_proctor', 'proview_callback_url'));
$auth_payload = new \stdClass();
$auth_payload->username = trim(get_config('quizaccess_proctor', 'proview_admin_username'));
$auth_payload->password = trim(get_config('quizaccess_proctor', 'proview_admin_password'));
$auth_response = self::generate_auth_token($api_base_url, $auth_payload);
$auth_token = $auth_response['access_token'];
$url = $api_base_url . '/proview/wrapper/create';

$blacklisted_softwares_mac = isset($quizaccess_proctor_setting->blacklisted_softwares_mac)
? array_filter((array) $quizaccess_proctor_setting->blacklisted_softwares_mac, function($item) {
return !empty($item);
})
: [];

$blacklisted_softwares_windows = isset($quizaccess_proctor_setting->blacklisted_softwares_win)
? array_filter((array) $quizaccess_proctor_setting->blacklisted_softwares_win, function($item) {
return !empty($item);
})
: [];

$data = array(
'session_external_id' => $proctoring_payload->session_id,
'attendee_external_id' => $proctoring_payload->profile_id,
'redirect_url' => $PAGE->url->__toString(),
'expiry' => date(DATE_ISO8601, $quiz->timeclose == 0 ? strtotime("+3 days") : $quiz->timeclose),
'is_secure_browser' => isset($quizaccess_proctor_setting->tsbenabled) ? boolval($quizaccess_proctor_setting->tsbenabled) : false,
"secure_browser" => [
"blacklisted_softwares_mac" => $blacklisted_softwares_mac,
"blacklisted_softwares_windows" => $blacklisted_softwares_windows,
"is_record_screen" => isset($quizaccess_proctor_setting->sb_content_protection) ? boolval($quizaccess_proctor_setting->sb_content_protection) : false,
"is_minimize" => isset($quizaccess_proctor_setting->sb_kiosk_mode) ? boolval($quizaccess_proctor_setting->sb_kiosk_mode) : false,
],
);

try {
$curl->setHeader(array('Content-Type: application/json', 'Authorization: Bearer ' . $auth_token));
$response = $curl->post($url, json_encode($data));
$decoded_response = json_decode($response, false);
return $decoded_response;
} catch (\Throwable $err) {
self::capture_error($err);
}
}



private static function generate_auth_token($api_base_url, $payload)

private static function generate_auth_token($api_base_url, $payload)
{
$curl = new \curl();
$headers = array('Content-Type: application/json');
Expand All @@ -115,7 +171,7 @@ private static function generate_auth_token($api_base_url, $payload)

private static function capture_error(\Throwable $err)
{
\Sentry\init(['dsn' => 'https://[email protected].sentry.io/5304587']);
\Sentry\init(['dsn' => 'https://577c4f60f7bd37671bdd8ad626d63a7d@sentry.talview.org/149']);
\Sentry\captureException($err);
}

Expand Down Expand Up @@ -148,6 +204,21 @@ public static function insert_tracking()
$attempt = $attempt->attempt;
}
$template->current_attempt = $attempt;
$quizaccess_proctor_setting = $DB->get_record('quizaccess_proctor', array('quizid' => $quiz->id));
if ($quizaccess_proctor_setting) {
$template->session_type = $quizaccess_proctor_setting->proctortype;
} else {
$template->session_type = "ai_proctor";
}
$template->session_id = $template->session_type === "live_proctor" ? $quiz->id.'-'.$USER->id : $quiz->id.'-'.$USER->id.'-'.$attempt;
if (strpos($PAGE->url, ('mod/quiz/attempt')) &&
$quizaccess_proctor_setting &&
$quizaccess_proctor_setting->proctortype == 'noproctor' &&
$quizaccess_proctor_setting->tsbenabled &&
strpos($_SERVER ['HTTP_USER_AGENT'], "Proview-SB") === FALSE) {
self::redirect_to_wrapper($template, $quiz, $quizaccess_proctor_setting);
return;
}

if (strpos($PAGE->url, ('mod/quiz/report'))) {
$quiz_attempts = $DB->get_records('quiz_attempts', array('quiz' => $quiz->id));
Expand All @@ -160,6 +231,7 @@ public static function insert_tracking()
$template->attempts = json_encode($quiz_attempts);
}
}

if ($pageinfo && !empty($template->token)) {
// The templates only contains a "{js}" block; so we don't care about
// the output; only that the $PAGE->requires are filled.
Expand Down
8 changes: 6 additions & 2 deletions classes/local/injector.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,15 @@ public static function inject() {
if ($PAGE->cm) {
$quiz = $DB->get_record('quiz', array('id' => $PAGE->cm->instance));
$quizaccess_proctor_setting = $DB->get_record('quizaccess_proctor', array('quizid' => $quiz->id));
//Logic for launching Secure Browser without Proctoring Starts
if ((!$quizaccess_proctor_setting) ||
($quizaccess_proctor_setting && $quizaccess_proctor_setting->proctortype == 'noproctor')) {
($quizaccess_proctor_setting && $quizaccess_proctor_setting->proctortype == 'noproctor' && $quizaccess_proctor_setting->tsbenabled)) {
$t = new api\tracker();
$t::insert_tracking();
self::inject_password($PAGE, $quiz);
return;
}
//Logic for launching Secure Browser without Proctoring Ends
}
// Logic for enabling proview for course level and quiz level ends.

Expand Down Expand Up @@ -126,7 +130,7 @@ public static function inject() {
$t::insert_tracking();
return;
} catch (\Throwable $error) {
\Sentry\init(['dsn' => 'https://[email protected].sentry.io/5304587' ]);
\Sentry\init(['dsn' => 'https://577c4f60f7bd37671bdd8ad626d63a7d@sentry.talview.org/149' ]);
\Sentry\captureException($error);
die;
?>
Expand Down
9 changes: 9 additions & 0 deletions datastore.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@
$template->profile_id = $USER->id;
$template->instructions = $quizaccess_proctor_setting->instructions;
$template->reference_link= $quizaccess_proctor_setting->reference_link;
if ($quizaccess_proctor_setting->tsbenabled === "1" ) {
$template->tsb_enabled = true;
$template->sb_blacklisted_software_windows= isset($quizaccess_proctor_setting->blacklisted_softwares_win) ? $quizaccess_proctor_setting->blacklisted_softwares_win : '';
$template->sb_blacklisted_software_mac= isset($quizaccess_proctor_setting->blacklisted_softwares_mac) ? $quizaccess_proctor_setting->blacklisted_softwares_mac : '';
$template->minimize_permitted= isset($quizaccess_proctor_setting->sb_kiosk_mode) ? !boolval($quizaccess_proctor_setting->sb_kiosk_mode) : true;
$template->screen_protection= isset($quizaccess_proctor_setting->sb_content_protection) ? boolval($quizaccess_proctor_setting->sb_content_protection) : false;
}else{
$template->tsb_enabled = false;
}
$template->session_id = $template->session_type === "live_proctor" ? $quizid.'-'.$USER->id : $quizid.'-'.$USER->id.'-'.$attempt; // Do not append attempt number for live proctoring. Re-attempting same quiz not supported in live proctoring.
$template->proview_url = trim(get_config('local_proview', 'proview_url'));
$template->token = trim(get_config('local_proview', 'token'));
Expand Down
48 changes: 35 additions & 13 deletions frame.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
<script>
var childOrigin = '*';
Sentry.init({
dsn: 'https://[email protected].sentry.io/5304587'
dsn: 'https://577c4f60f7bd37671bdd8ad626d63a7d@sentry.talview.org/149'
});
// Defining function for event handling on postMessage from any window
function receiveMessage(event) {
Expand All @@ -84,17 +84,20 @@ function receiveMessage(event) {


function startProview(
authToken,
profileId,
session,
session_type = "ai_proctor",
proview_url,
authToken,
profileId,
session,
session_type,
proview_url,
additionalInstruction,
reference_link,
proview_playback_url,
skipHardwareTest,
previewStyle,
clear) {
blacklistedSoftwaresWindows,
blacklistedSoftwaresMac,
isScreenProtectionEnabled,
minimizeOption,
tsbenabled
) {
const referenceLinksArray = reference_link.match(/\[([^\]]+)\]\(([^)]+)\)/g)?.map(markdownLink => {
const match = markdownLink.match(/\[([^\]]+)\]\(([^)]+)\)/);
if (match) {
Expand All @@ -117,9 +120,14 @@ function startProview(
session_type: session_type,
additionalInstruction: additionalInstruction,
referenceLinks: JSON.stringify(referenceLinksArray),
clear: clear || false,
skipHardwareTest: skipHardwareTest || false,
previewStyle: previewStyle || 'position: fixed; bottom: 0px;',
clear: false,
skipHardwareTest: false,
previewStyle: 'position: fixed; bottom: 0px;',
enforceTSB: tsbenabled,
blacklistedSoftwaresWindows: blacklistedSoftwaresWindows,
blacklistedSoftwaresMac: blacklistedSoftwaresMac,
isScreenProtectionEnabled: isScreenProtectionEnabled,
minimizeOption: minimizeOption,
initCallback: createCallback(proview_playback_url, profileId, session_type)/* onProviewStart */
});
}
Expand Down Expand Up @@ -220,7 +228,21 @@ function run(){
response=xmlhttp.responseText;
response=JSON.parse(response);
window.quizPassword = response.quiz_password;
startProview(response.token, response.profile_id, response.session_id, response.session_type, response.proview_url, response.instructions, response.reference_link, response.proview_playback_url);
startProview(
response.token,
response.profile_id,
response.session_id,
response.session_type,
response.proview_url,
response.instructions,
response.reference_link,
response.proview_playback_url,
response.sb_blacklisted_software_windows,
response.sb_blacklisted_software_mac,
response.screen_protection,
response.minimize_permitted,
response.tsb_enabled
);
}
}
xmlhttp.open("GET", "datastore.php?quiz_id=" + urlParams.get('quizId') + "&sesskey=" + "<?php echo $sesskey?>" , true);
Expand Down
18 changes: 16 additions & 2 deletions templates/tracker.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
parent.postMessage({type: 'stopProview',url: window.location.href}, childOrigin);
}
})
if( window.self == window.top) {
if( window.self == window.top && '{{session_type}}' !== 'noproctor') {
let params = new URLSearchParams({
url: current,
clear: false,
Expand All @@ -97,6 +97,20 @@
});
window.location.href = '{{root_dir}}local/proview/frame.php?'+params.toString();
}
// Hide back Button from quiz if exam is launched in SB Starts
// NOTE: This will only work for English Language due to string matching
if (window.navigator.userAgent.match(/Proview-SB/)) {
console.log("User Agent: ", window.navigator.userAgent);
document.querySelectorAll('a').forEach(link => {
const urlContainsIndex = link.href.includes('moodle/mod/quiz/view.php');
const textContainsBack = link.textContent.trim().toLowerCase().includes('back');
if (urlContainsIndex && textContainsBack) {
// Hide the element by setting its display style to none
link.style.display = 'none';
}
});
}
// Hide back Button from quiz if exam is launched in SB Starts
}
if(current.match('mod/quiz/view') ) {
if(window.self != window.top && window.parent.previousUrl) { //checking if the request is coming from summary
Expand All @@ -108,7 +122,7 @@
let div = document.getElementsByClassName("singlebutton quizstartbuttondiv");
div[0].getElementsByTagName("*")[3].addEventListener("click", function(event){
$(".moodle-dialogue-base").hide();
if( window.self == window.top) {
if( window.self == window.top && '{{session_type}}' !== 'noproctor') {
let params = new URLSearchParams({
url : current,
clear : false,
Expand Down
4 changes: 2 additions & 2 deletions version.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@

defined('MOODLE_INTERNAL') || die;

$plugin->version = 2024081301;
$plugin->version = 2024110501;
$plugin->requires = 2020061500;
$plugin->release = '3.3.4 (Build: 2024081301)';
$plugin->release = '3.3.8 (Build: 2024110501)';
$plugin->maturity = MATURITY_STABLE;
$plugin->component = 'local_proview';

Expand Down

0 comments on commit 1e0347b

Please sign in to comment.