Index: openacs-4/packages/proctoring-support/proctoring-support.info =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/proctoring-support.info,v diff -u -r1.1.2.16 -r1.1.2.17 --- openacs-4/packages/proctoring-support/proctoring-support.info 14 Jun 2021 15:35:18 -0000 1.1.2.16 +++ openacs-4/packages/proctoring-support/proctoring-support.info 16 Jun 2021 11:26:54 -0000 1.1.2.17 @@ -10,7 +10,7 @@ f proctoring - + Antonio Pisano Set of tools to implement proctoring of user interaction Wirtschaftsuniversität Wien @@ -21,7 +21,7 @@ No real UI is provided by the package itself. Other packages must integrate the provided includes. 0 - + Index: openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-create.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-create.sql,v diff -u -r1.1.2.4 -r1.1.2.5 --- openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-create.sql 9 Jun 2021 15:55:53 -0000 1.1.2.4 +++ openacs-4/packages/proctoring-support/sql/postgresql/proctoring-support-create.sql 16 Jun 2021 11:26:54 -0000 1.1.2.5 @@ -54,17 +54,13 @@ proctoring_examination_statement_acceptance(user_id); create table proctoring_safe_exam_browser_conf ( - object_id integer primary key - references acs_objects(object_id) on delete cascade, - seb_file text not null -- the file created via the SEB + object_id integer primary key references acs_objects(object_id) on delete cascade, + seb_file text -- the file created via the SEB -- exam configuration that will -- configure the clients -- accessing this proctored -- object - key text not null, -- the keys generated during the SEB + allowed_keys text not null, -- the keys generated during the SEB -- configuration that have been allowed -- access to this exam ); - -create index proctoring_safe_exam_browser_conf_object_id_idx on - proctoring_safe_exam_browser_conf(object_id); Fisheye: Tag 1.1 refers to a dead (removed) revision in file `openacs-4/packages/proctoring-support/sql/postgresql/upgrade/upgrade-1.5.1-2.0.0.sql'. Fisheye: No comparison available. Pass `N' to diff? Index: openacs-4/packages/proctoring-support/tcl/proctoring-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/tcl/proctoring-procs.tcl,v diff -u -r1.1.2.12 -r1.1.2.13 --- openacs-4/packages/proctoring-support/tcl/proctoring-procs.tcl 9 Jun 2021 15:55:54 -0000 1.1.2.12 +++ openacs-4/packages/proctoring-support/tcl/proctoring-procs.tcl 16 Jun 2021 11:26:55 -0000 1.1.2.13 @@ -47,7 +47,7 @@ {-start_time ""} {-end_time ""} {-seb_p false} - {-seb_key ""} + {-seb_keys ""} {-seb_file ""} } { Configures proctoring for specified object. @@ -74,9 +74,12 @@ executed. No time check when not specified. @param seb_p Does this object enforce the use of the Safe Exam Browser? - @param seb_key Key we checking against when enforcing the use of - the Safe Exam Browser, created via the Safe Exam - Browser configuration tool. + @param seb_keys Keys we check against when enforcing the use of + the Safe Exam Browser, created via the Safe Exam + Browser configuration tool. These can be a + ConfigKeyHash, just validating the browser's + configuration or a RequestHash, also validating + the platform-specific version of SEB in use. @param seb_file .seb file that holds the valid configuration for this exam. When provided, upon failing the check the user will be sent the file so that they can @@ -127,11 +130,10 @@ } if {$seb_p} { - if {$seb_key ne "" && - $seb_file ne ""} { + if {$seb_keys ne ""} { ::proctoring::seb::configure \ -object_id $object_id \ - -key $seb_key \ + -allowed_keys $seb_keys \ -seb_file $seb_file } } else { @@ -162,7 +164,7 @@ set proctoring_p false set examination_statement_p false set seb_p false - set seb_key "" + set seb_keys {} set seb_file "" ::xo::dc 0or1row is_proctored { @@ -178,7 +180,7 @@ case when enabled_p then 'true' else 'false' end as enabled_p, case when examination_statement_p then 'true' else 'false' end as examination_statement_p, case when seb.object_id is not null then 'true' else 'false' end as seb_p, - seb.key as seb_key, + seb.allowed_keys as seb_keys, seb.seb_file from proctoring_objects o left join proctoring_safe_exam_browser_conf seb @@ -199,7 +201,7 @@ proctoring_p $proctoring_p \ examination_statement_p $examination_statement_p \ seb_p $seb_p \ - seb_key $seb_key \ + seb_keys $seb_keys \ seb_file $seb_file] } Index: openacs-4/packages/proctoring-support/tcl/safe-exam-browser-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/tcl/safe-exam-browser-procs.tcl,v diff -u -r1.1.2.3 -r1.1.2.4 --- openacs-4/packages/proctoring-support/tcl/safe-exam-browser-procs.tcl 14 Jun 2021 15:35:18 -0000 1.1.2.3 +++ openacs-4/packages/proctoring-support/tcl/safe-exam-browser-procs.tcl 16 Jun 2021 11:26:55 -0000 1.1.2.4 @@ -9,38 +9,42 @@ ad_proc -private ::proctoring::seb::configure { -object_id:required - -key:required - -seb_file:required + -allowed_keys:required + {-seb_file ""} } { Stores the configuration for an exam @param object_id id of the proctored object. - @param key safe exam browser's key that will be used to validate + @param allowed_keys safe exam browser's keys that will be used to validate against the request hash provided by the clients. @param seb_file absolute path to a file that will store the configuration for this exam. When served to a user having the Safe Exam Browser installed, this file will configure and optionally start the exam using exactly the provided configuration. } { - set folder_path [acs_root_dir]/proctoring/seb/$object_id - file mkdir -- $folder_path + if {$seb_file ne ""} { + set folder_path [acs_root_dir]/proctoring/seb/$object_id + file mkdir $folder_path - set seb_file_path $folder_path/Conf.seb + set seb_file_path $folder_path/Conf.seb - file rename -force -- $seb_file $seb_file_path + file rename -force -- $seb_file $seb_file_path + } else { + set seb_file_path "" + } ::xo::dc dml -prepare { integer text text text text } save_conf { insert into proctoring_safe_exam_browser_conf - (object_id, key, seb_file) + (object_id, allowed_keys, seb_file) values - (:object_id, :key, :seb_file_path) + (:object_id, :allowed_keys, :seb_file_path) on conflict (object_id) do update set - key = :key, + allowed_keys = :allowed_keys, seb_file = :seb_file_path } } @@ -71,29 +75,73 @@ Validates a Safe Exam Browser hash. The hash is generated based on: - - the SEB configuration - - (optionally) the SEB version and platform + - the SEB configuration (ConfigKeyHash and RequestHash) + - the SEB version and platform (RequestHash only) - the currently requested URL @return boolean } { return [expr {[ns_md string -digest sha256 ${url}${key}] eq $hash}] } -ad_proc -private ::proctoring::this_url {} { +ad_proc -private ::proctoring::seb::this_url {} { Computes the currently requested URL, used to match against the hash provided by the browser. @return fully qualified URL } { set url [util_current_location][ns_conn url] - if {[ns_conn query] ne ""} { - append url ?[ns_conn query] + set query [ns_conn query] + if {$query ne ""} { + append url ?$query } return $url } +ad_proc -private ::proctoring::seb::get_hashes {} { + Gets the hashes provided by the browser, which we will check + against the configured keys. +} { + set hashes [list] + + foreach h { + "X-SafeExamBrowser-RequestHash" + "X-SafeExamBrowser-ConfigKeyHash" + } { + set hash [ns_set get [ns_conn headers] $h] + if {$hash ne ""} { + lappend hashes $hash + } + } + + return $hashes +} + +ad_proc -private ::proctoring::seb::valid_access_p { + -allowed_keys:required +} { + Check the hashes provided by the browser against the keys. + + @return boolean +} { + set url [::proctoring::seb::this_url] + + foreach hash [::proctoring::seb::get_hashes] { + foreach key $allowed_keys { + set valid_access_p [::proctoring::seb::valid_hash_p \ + -hash $hash \ + -url $url \ + -key $key] + if {$valid_access_p} { + return true + } + } + } + + return false +} + ad_proc -private ::proctoring::seb::require_valid_access { -object_id:required } { @@ -104,25 +152,22 @@ unauthorized error. } { set seb_p [::xo::dc 0or1row -prepare integer get_seb_conf { - select key, seb_file + select allowed_keys, seb_file from proctoring_safe_exam_browser_conf where object_id = :object_id }] if {$seb_p} { - set hash [ns_set get [ns_conn headers] X-SafeExamBrowser-RequestHash] - set url [::proctoring::this_url] - set valid_access_p [::proctoring::seb::valid_hash_p \ - -hash $hash \ - -url $url \ - -key $key] + set valid_access_p [::proctoring::seb::valid_access_p \ + -allowed_keys $allowed_keys] } else { set valid_access_p true } if {!$valid_access_p} { if {[file exists $seb_file]} { - ns_set cput [ns_conn outputheaders] Content-Disposition "attachment; filename=Config.seb" + ns_set cput [ns_conn outputheaders] \ + Content-Disposition "attachment; filename=[file tail $filename]" ns_writer submitfile -headers $seb_file } else { ns_returnunauthorized Index: openacs-4/packages/proctoring-support/tcl/test/proctoring-test-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/tcl/test/proctoring-test-procs.tcl,v diff -u -r1.1.2.11 -r1.1.2.12 --- openacs-4/packages/proctoring-support/tcl/test/proctoring-test-procs.tcl 31 May 2021 12:40:02 -0000 1.1.2.11 +++ openacs-4/packages/proctoring-support/tcl/test/proctoring-test-procs.tcl 16 Jun 2021 11:26:55 -0000 1.1.2.12 @@ -51,6 +51,7 @@ ::proctoring::configure ::proctoring::get_configuration ::proctoring::active_p + ::proctoring::seb::valid_hash_p } \ proctoring_conf_test { Test proctoring configuration api @@ -78,6 +79,9 @@ desktop_p proctoring_p examination_statement_p + seb_p + seb_file + seb_keys } { aa_true "Field $field exists in dict" [dict exists $conf $field] } @@ -141,6 +145,58 @@ ::proctoring::configure -object_id $object_id -camera_p true set conf [::proctoring::get_configuration -object_id $object_id] aa_true "Now proctoring appears to be on" [dict get $conf proctoring_p] + + aa_log "Storing SEB configuration" + set key a3e85dcad0cd6a6e2f55e77399e4c9caf47807d760402d6b740017a9f0b2a197 + set hash 6f3edc0ef5a56879eba206a7debb3fb0585ebb1f2423ebc10a1afce991edfbcd + set url https://learn-a.wu.ac.at:8081/dotlrn/classes/tlf/testkurs.17s/ + set conf_file [ad_tmpnam] + set wfd [open $conf_file w] + puts $wfd abcd + close $wfd + set conf_file_hash [ns_md file $conf_file] + + ::proctoring::configure \ + -object_id $object_id \ + -seb_p true \ + -seb_keys $key + set conf [::proctoring::get_configuration -object_id $object_id] + aa_equals "Conf file is empty" [dict get $conf seb_file] "" + aa_equals "Key has been stored" [dict get $conf seb_keys] $key + + set keys [list $key ${key}-2 ${key}-3] + ::proctoring::configure \ + -object_id $object_id \ + -seb_p true \ + -seb_keys $keys + set conf [::proctoring::get_configuration -object_id $object_id] + aa_equals "Same number of keys are stored" [llength $keys] [llength [dict get $conf seb_keys]] + aa_equals "Exactly the same keys are stored" [lsort $keys] [lsort [dict get $conf seb_keys]] + + ::proctoring::configure \ + -object_id $object_id \ + -seb_keys ${key}abcd + set conf [::proctoring::get_configuration -object_id $object_id] + aa_equals "Seb confs are deleted when the seb_p flag is false" \ + "" [dict get $conf seb_keys] + + ::proctoring::configure \ + -object_id $object_id \ + -seb_p true \ + -seb_keys $key \ + -seb_file $conf_file + + set conf [::proctoring::get_configuration -object_id $object_id] + aa_equals "Conf file was stored correctly" \ + [ns_md file [dict get $conf seb_file]] $conf_file_hash + aa_equals "Key was stored correctly" \ + [dict get $conf seb_keys] $key + + aa_true "Data has been stored correctly and the hash can be computed as expected" \ + [::proctoring::seb::valid_hash_p \ + -key [lindex [dict get $conf seb_keys] 0] \ + -hash $hash \ + -url $url] } } @@ -207,6 +263,7 @@ file delete -- $file1 $file2 } } + # Local variables: # mode: tcl # tcl-indent-level: 4