Compare commits

...

541 commits

Author SHA1 Message Date
Damian Büchel
7029b12d81 SEBWIN-935: Removed old browser engine runtime references. 2024-09-09 15:08:39 +02:00
Damian Büchel
9762019499 SEBWIN-936: Resolved errors and improved transmission spooler, reduced and improved logging of service and server proxies resp. requests, specified timeout for service requests and fixed missing cache path in proctoring finalization dialog. 2024-09-04 15:14:55 +02:00
Damian Büchel
d5b182ae2f SEBWIN-852: Ensured page zoom via CTRL+MOUSEWHEEL also works according to the respective configuration value. 2024-08-29 10:08:36 +02:00
Damian Büchel
4c0f3cfa6c SEBWIN-934: Ensured window title of active application is always current and fixed encoding of screen shot metadata. 2024-08-28 14:57:20 +02:00
Damian Büchel
f096b96741 SEBWIN-925: Reintegrated Themida. 2024-08-27 17:46:25 +02:00
Damian Büchel
50ac28f9ea SEBWIN-925: Removed Themida. 2024-08-22 16:29:47 +02:00
Damian Büchel
144c3ba752 Minor refactoring. 2024-08-22 16:26:58 +02:00
Damian Büchel
21353e6d6d SEBWIN-865: Integrated final screen proctoring notification icons. 2024-08-21 15:15:51 +02:00
Damian Büchel
26f14f235d SEBWIN-931: Added missing default settings related to clipboard and ensured data processor is also executed with default settings. 2024-08-19 12:19:32 +02:00
Damian Büchel
1ff7d84375 Corrected comment for ImageQuantiziation.Color16bpp. 2024-08-16 11:08:14 +02:00
Damian Büchel
febfd944e0 SEBWIN-923: Removed hardcoded client credentials for SPS and used actual ones from join instruction. 2024-08-12 14:42:09 +02:00
Damian Büchel
a1bfaadcd9 SEBWIN-882, SEBWIN-904, #914: Ensured monitoring is terminated before reconfiguration. 2024-07-29 16:37:17 +02:00
Damian Büchel
0b1746a82e SEBWIN-916: Ensured timestamp of a screen shot request is capture and not transmission time. 2024-07-25 17:41:55 +02:00
Damian Büchel
ede6a926cc SEBWIN-913: Removed URLs from browser meta data for screen proctoring. 2024-07-25 17:14:17 +02:00
Damian Büchel
d4f5f203db SEBWIN-909: Ensured individual keys are not transmitted as part of the screen proctoring meta data and improved presentation of single modifier key triggers. 2024-07-25 16:49:52 +02:00
Damian Büchel
a350949b1b SEBWIN-917: Consolidated detectors in monitoring assembly. 2024-07-25 15:30:56 +02:00
Damian Büchel
6a77a41564 SEBWIN-917: Consolidated system (events) monitoring in sentinel. 2024-07-25 12:22:49 +02:00
Damian Büchel
1f50ab74c9 SEBWIN-902: Implemented fix for GHSA-9cr5-q96r-887f and refactored various integrity aspects. 2024-07-24 20:31:08 +02:00
Damian Büchel
b48ef21708 SEBWIN-771: Implemented reconfiguration safeguard and refactored and moved reconfiguration and session locking to new coordinator module. 2024-07-15 18:34:30 +02:00
Damian Büchel
f3a9030505 SEBWIN-914: Reactivated disclaimer for screen proctoring. 2024-07-09 13:38:04 +02:00
Damian Büchel
89091acaac SEBWIN-914: Build without disclaimer for SPS load tests. 2024-07-09 13:36:17 +02:00
Damian Büchel
04843d3fa8 SEBWIN-907: Fixed bug where start URL query parameters wouldn't be applied when using SEB Server. 2024-07-01 15:25:08 +02:00
Damian Büchel
a3c9271faf SEBWIN-742: Improved build log for server environment. 2024-06-25 17:21:33 +02:00
Damian Büchel
68d6d47fe6 SEBWIN-742: Integrated Themida into build process. 2024-06-24 14:40:42 +02:00
Damian Büchel
b4366adab1 SEBWIN-896, #805: Removed duplicated entries for default list of prohibited applications. 2024-06-17 12:12:36 +02:00
Damian Büchel
c23b78488c SEBWIN-897: Corrected default values for down- and uploads. 2024-06-13 17:34:36 +02:00
Damian Büchel
58c8e69716 SEBWIN-893, #883: Implemented unit test for concurrency issue resolution. 2024-06-13 15:29:58 +02:00
Damian Büchel
0fb7f23bcb SEBWIN-836: Grouped all settings related to the user interface. 2024-06-12 18:18:52 +02:00
Damian Büchel
05f46cd6b4 SEBWIN-836: Implemented configuration value for lock screen background color. 2024-06-12 17:30:19 +02:00
Damian Büchel
f2798581a4 Merge branch 'master' of https://github.com/SafeExamBrowser/seb-win-refactoring 2024-06-10 19:40:00 +02:00
Damian Büchel
471e69d460 SEBWIN-788: Improved network adapter implementation. 2024-06-10 19:39:58 +02:00
Damian Büchel
62dc690a52
Updated security policy. 2024-06-10 10:13:08 +02:00
Damian Büchel
a41b40d428
Updated security policy. 2024-06-10 10:11:49 +02:00
Damian Büchel
8cf214b39c
Created security policy. 2024-06-10 10:08:20 +02:00
Damian Büchel
04dce13d86 SEBWIN-893, #883: Attempt to fix possible concurrency issue with (configuration) key hash calculation. 2024-06-06 18:47:04 +02:00
Damian Büchel
767ac84391 SEBWIN-844, #790: Implemented configuration option for session integrity verification. 2024-06-05 19:30:35 +02:00
Damian Büchel
84bbcb82ef SEBWIN-788: Implemented automatic connection attempt and retry on invalid credentials. Improved wording of username label in credentials dialog. 2024-06-03 19:41:54 +02:00
Damian Büchel
b3228aedef SEBWIN-782, #703: Ensured browser session remains active after reconfiguration by browser resource. 2024-05-24 15:46:01 +02:00
Damian Büchel
3b099688f7 SEBWIN-849: Implemented index suffix for already existing files when downloading. 2024-05-22 15:25:52 +02:00
Damian Büchel
639700abd8 Merge branch 'master' of https://github.com/SafeExamBrowser/seb-win-refactoring 2024-05-21 19:11:43 +02:00
Damian Büchel
473edc7a2e SEBWIN-788: Finished implementation of new (wireless) network adapter and authentication functionality. 2024-05-21 19:11:42 +02:00
Damian Büchel
60ee95a9ee
Removed WebView2 dependency. 2024-05-07 11:49:36 +02:00
Damian Büchel
1edde7b6f5
Updated build server URL. 2024-05-07 11:48:10 +02:00
Damian Büchel
4015e9a574 SEBWIN-788: Implemented scaffolding for wireless network credentials. 2024-05-02 10:30:26 +02:00
Damian Büchel
bbb5ec2571
Merge pull request #852 from Notselwyn/patch-1
SEBWIN-789: remove historic hw VM check
2024-04-22 18:33:38 +02:00
Lau
8b3f9b0838
Update VirtualMachineDetector.cs 2024-04-22 11:26:20 +02:00
Damian Büchel
e4a82e2f63 SEBWIN-795: Improved user session resolution with SEB Server. 2024-04-22 11:09:37 +02:00
Damian Büchel
01db8fd84e SEBWIN-878, #848: Fixed session cookie name for user resolution with Moodle and SEB Server. 2024-04-17 12:05:34 +02:00
Damian Büchel
a397446252 Added KGy SOFT to license and version information. 2024-04-17 09:38:18 +02:00
Damian Büchel
e8ebd2840e SEBWIN-833: Completely deleted all Jitsi Meet and Zoom video proctoring code and removed WebView2 dependency. 2024-04-17 09:19:18 +02:00
Damian Büchel
d9662ec31e
Added known issues to exemptions for issue maintenance. 2024-04-11 12:07:56 +02:00
Damian Büchel
c2f61ea6ab SEBWIN-871: Fixed unit tests due to proctoring implementation changes. 2024-04-04 17:36:19 +02:00
Damian Büchel
7801d68b97 Updated version to 3.8.0 beta. 2024-04-04 17:26:11 +02:00
Damian Büchel
ff16743ae7 Release build of version 3.7.0. 2024-04-03 09:20:33 +02:00
Damian Büchel
832eee17d5 SEBWIN-866: Replaced screen proctoring disclaimer and translated missing text to Chinese. 2024-03-28 11:23:30 +01:00
Damian Büchel
2c39668667 Reactivated disclaimer for screen proctoring. 2024-03-14 14:41:27 +01:00
Damian Büchel
577a23b8b4 Build without disclaimer for SPS load tests. 2024-03-14 14:24:11 +01:00
Damian Büchel
514414e322 SEBWIN-820, #764: Fixed missing content of isolated clipboard after navigation or reload. 2024-03-13 17:38:10 +01:00
Damian Büchel
4b222df6c5 SEBWIN-850: Added missing indirect wired video output technology. 2024-03-13 13:15:15 +01:00
Damian Büchel
acb9b97854 SEBSP-110: Translated remaining text for proctoring finalization dialog. 2024-03-11 11:50:30 +01:00
Damian Büchel
8a47228881 SEBSP-110: Added screen proctoring disclaimer. 2024-03-11 09:55:44 +01:00
Damian Büchel
ff33394565 SEBWIN-824: Updated year in license and copyright remarks. Removed old CEF redistributable packages, see https://github.com/cefsharp/CefSharp/issues/4704. 2024-03-05 18:37:42 +01:00
Damian Büchel
e6e0cca292 SEBWIN-824: Changed department from LET to ID in copyright notice. 2024-03-05 18:13:14 +01:00
Damian Büchel
3f90d3a58a Updated solution dependencies and browser engine (version 121.3.130). 2024-03-05 17:41:40 +01:00
Damian Büchel
956771c0e7 SEBWIN-855, SEBWIN-856: Disabled video proctoring with Jitsi Meet and Zoom. 2024-03-05 17:32:42 +01:00
Damian Büchel
9bbcaa2a98 SEBWIN-816, #755: Introduced new configuration key to control verification of cursor configuration. 2024-03-05 16:39:21 +01:00
Damian Büchel
a81837faa0 SEBWIN-826: Added new configuration keys to allow down- and uploads separately and disabled down- and uploads by default. 2024-03-05 11:04:54 +01:00
Damian Büchel
c46d1a3ade Removed unused code. 2024-03-04 17:34:37 +01:00
Damian Büchel
a1d62dd3de Reverted configuration overrides for debugging. 2024-03-04 14:34:35 +01:00
Damian Büchel
9045b852d0 SEBWIN-820, #764: Implemented cross-window sharing of clipboard content for isolated clipboard policy. 2024-03-04 14:27:49 +01:00
Damian Büchel
ff5b91c010 SEBSP-107: Implemented screen proctoring finalization. 2024-02-29 21:05:43 +01:00
Damian Büchel
787c84cc0e SEBWIN-842: Ensured pinch zooming also respects the zooming configuration value. 2024-02-27 10:42:02 +01:00
Damian Büchel
0777644f0e SEBSP-107: Added property to control activation of notifications and created placeholder for screen proctoring notification icons. 2024-02-23 18:32:44 +01:00
Damian Büchel
e5c02a1f74 SEBSP-107: Implemented resp. improved configuration for metadata capturing. 2024-02-22 18:04:00 +01:00
Damian Büchel
91f2c14a77 SEBSP-107: Removed logging of browser URLs in metadata aggregator. 2024-02-22 17:29:17 +01:00
Damian Büchel
4aacf85e9a SEBSP-23: Removed service health simulation and changed health value to normally be retrieved from transmission response. 2024-02-21 19:17:08 +01:00
Damian Büchel
a021bebde6 Merge branch 'master' of https://github.com/SafeExamBrowser/seb-win-refactoring 2024-02-21 18:37:24 +01:00
Damian Büchel
f902ee9598 SEBSP-23: Finished basic network redundancy. This build contains a service health simulation. 2024-02-21 18:37:23 +01:00
Damian Büchel
ea9d7e0de7
Reverted back to version 8 of stale workflow due to a bug in their caching implementation (see https://github.com/actions/stale/issues/1133). 2024-02-21 10:23:55 +01:00
Damian Büchel
a213ec0f7d SEBSP-23: Implemented scaffolding for network redundancy. 2024-02-16 19:42:41 +01:00
Damian Büchel
731a748552 SEBSP-61: Implemented basic metadata collection & transmission. 2024-02-13 11:04:36 +01:00
Damian Büchel
cb81906945 SEBSP-26: Implemented capturing and transmission interval. 2024-02-06 10:45:45 +01:00
Damian Büchel
456894edb9 Merge branch 'master' of https://github.com/SafeExamBrowser/seb-win-refactoring 2024-02-01 17:36:13 +01:00
Damian Büchel
acb6b8cf09 SEBSP-15, SEBSP-70: Implemented basic screen proctoring functionality including image format & quantization settings. 2024-02-01 17:36:11 +01:00
Damian Büchel
6b40b64590
Updated bug report template and added remark about log files. 2024-01-31 14:40:22 +01:00
Damian Büchel
70ba9ad7b6 SEBWIN-834: Fixed duplicate use of terminate method for proctoring implementations. 2024-01-22 10:38:05 +01:00
Damian Büchel
b62a8bdfe3
Added ability to manually trigger issue maintenance workflow. 2024-01-19 10:48:34 +01:00
Damian Büchel
654aa14bee
Added "bug" to exempt labels for maintenance workflow. 2024-01-19 10:43:15 +01:00
Damian Büchel
1925231a19
Added exempt issue labels for maintenance workflow. 2024-01-19 10:36:46 +01:00
Damian Büchel
55c36f6d7d Merge branch 'master' of https://github.com/SafeExamBrowser/seb-win-refactoring 2024-01-18 18:02:23 +01:00
Damian Büchel
de5691cb25 SEBWIN-834: Revised proctoring architecture to allow for simultaneous activation of different implementations. 2024-01-18 18:02:21 +01:00
Damian Büchel
93cee788f8
Created issue maintenance workflow. 2024-01-18 13:43:36 +01:00
Damian Büchel
73fefad434 SEBWIN-835: Removed debugging logs for power supply thresholds. 2024-01-17 20:28:23 +01:00
Damian Büchel
23de2bf8c7 SEBWIN-835: Fixed power supply threshold issue by parsing configuration values of type "real" with invariant culture. 2024-01-17 14:40:28 +01:00
Damian Büchel
96f67c2085 SEBWIN-835: Implemented further logging for power supply threshold issue. 2024-01-17 11:17:38 +01:00
Damian Büchel
c9db98159d SEBWIN-835: Implemented further logging for power supply threshold issue. 2024-01-17 11:08:03 +01:00
Damian Büchel
c52c461dbf SEBWIN-835: Added debugging logs for power supply thresholds. 2024-01-16 18:01:18 +01:00
Damian Büchel
cecfe095a7 SEBWIN-827: Removed obsolete todo. 2024-01-16 13:59:15 +01:00
Damian Büchel
5f6a57cd24 Increased spacing between lock screen options. 2024-01-15 12:17:07 +01:00
Damian Büchel
1e9d37ac13 SEBWIN-830, #747, #777: Fixed issue with registry monitoring and minor improvements. 2024-01-15 12:16:30 +01:00
Damian Büchel
ef267ef186
Merge pull request #777 from Notselwyn/issue-747-fix
Issue 747 fix
2024-01-15 09:51:54 +01:00
Damian Büchel
ecc8416dfb
Merge pull request #775 from kiraware/add-id-translation
add Bahasa Indonesia (id) translation
2024-01-12 15:19:03 +01:00
Damian Büchel
eae6ab1bdf
Merge pull request #770 from NekoJonez/patch-2
Blocking Groove/VLC to play music in the background
2024-01-12 14:57:57 +01:00
Damian Büchel
27155a057d SEBWIN-821: Implemented configuration value for lock screen on user session change. 2024-01-11 19:01:56 +01:00
Damian Büchel
79dedf12b5 SEBWIN-821: Implemented configuration values for critical and low battery charge thresholds. 2024-01-11 17:35:52 +01:00
Damian Büchel
8c45af88fb SEBWIN-821: Forgot to implement default values and data mapping for always on configuration. 2024-01-11 12:32:48 +01:00
Damian Büchel
181346b810 SEBWIN-821: Implemented always on configuration for display and system. 2024-01-11 12:02:01 +01:00
Notselwyn
04571f51b2 fix: fixed obj != obj checking (according to devops warnings) 2023-12-29 19:35:32 +01:00
Notselwyn
ebca114c2e fix: optimized (now redundant) code 2023-12-28 16:15:46 +01:00
Notselwyn
98fb7a32db fix: return val is not true when registry val does not exist 2023-12-28 16:11:26 +01:00
Kira
1aa32403a7 add Bahasa Indonesia (id) translation 2023-12-28 10:35:00 +08:00
Pieterjan Deneys
4e152c26f1
Update DataValues.cs 2023-12-16 12:52:51 +01:00
Pieterjan Deneys
945b9223e7
Update SEBSettings.cs 2023-12-16 12:49:45 +01:00
Damian Büchel
622df39fca
Removed codeql/csharp-queries. 2023-12-11 13:03:10 +01:00
Damian Büchel
5ef03d4101
Dito. 2023-12-11 12:55:48 +01:00
Damian Büchel
bc8235951d
Attempt to get C# queries working. 2023-12-11 12:50:20 +01:00
Damian Büchel
ae352a883c
Created CodeQL configuration. 2023-12-11 12:41:34 +01:00
Damian Büchel
7f4aee9058 SEBWIN-808, #716: Integrated Estonian translation. 2023-12-05 13:56:21 +01:00
Damian Büchel
c535124575 SEBWIN-804, #725, #727: Minor refactoring. 2023-11-24 11:41:12 +01:00
Damian Büchel
54b4444f8e
Merge pull request #728 from NekoJonez/patch-2
OBS on the default blocklist
2023-11-24 11:37:42 +01:00
Damian Büchel
51c21f8934
Merge branch 'master' into patch-2 2023-11-24 11:37:19 +01:00
Damian Büchel
f8b354623a
Merge pull request #726 from NekoJonez/patch-1
Adding the new Teams.exe (MS-Teams) to the default block list
2023-11-24 11:35:45 +01:00
Damian Büchel
5a58a11dd0 SEBWIN-803, #720, #723: Integrated Russian translation. 2023-11-24 11:08:29 +01:00
Damian Büchel
e9976ac158 Merge branch 'master' of https://github.com/SafeExamBrowser/seb-win-refactoring 2023-11-24 10:42:05 +01:00
Damian Büchel
fb70c9c928
Merge pull request #723 from IlmirSharifullin/master
Russian language
2023-11-24 10:39:22 +01:00
Pieterjan Deneys
09750cefd0
OBS no 2023-11-24 10:21:34 +01:00
Pieterjan Deneys
a286b615f4
Added OBS to the default blocklist 2023-11-24 10:15:56 +01:00
Pieterjan Deneys
e563767d6e
Add the new Teams.exe 2023-11-24 09:45:11 +01:00
Pieterjan Deneys
6f175bf0e7
Add the new Team exe 2023-11-24 09:43:17 +01:00
Ilmir Sharifullin
cb47c985e9 russian xml file 2023-11-24 00:03:23 +03:00
Damian Büchel
af5e33c2d8 SEBWIN-801: Fixed bug with ease of access configuration verification. 2023-11-23 18:00:35 +01:00
Damian Büchel
afe8b4bcca Updated version to 3.7.0 beta. 2023-11-22 11:04:18 +01:00
Damian Büchel
c6a8996138
Replaced disclaimer regarding development builds with blockquote alert. 2023-11-21 16:12:16 +01:00
Damian Büchel
bfd1da3a86 SEBWIN-793, #669: Fixed false positive for VirtualBox host systems. 2023-11-21 15:53:58 +01:00
Damian Büchel
8e17504d35 Integrated Dutch translation. 2023-11-20 17:15:22 +01:00
Damian Büchel
499629d848
Merge pull request #715 from NekoJonez/master
Dutch/Flemish translation v1
2023-11-20 17:12:25 +01:00
Pieterjan Deneys
25cb91899c
Dutch/Flemish translation v1 2023-11-20 16:03:55 +01:00
Damian Büchel
00a562b3c1 SEBWIN-783: Implemented error message when Zoom proctoring active. 2023-11-08 18:07:30 +01:00
Damian Büchel
a3d0ab433b SEBWIN-774: Updated browser engine to version 118.6.80. 2023-11-02 14:59:19 +01:00
Damian Büchel
751bfcb144 SEBWIN-762: Added user identifier detection via Moodle plugin and overall renamed session to user identifier. 2023-11-01 13:52:39 +01:00
Damian Büchel
8c3d9a31d7 SEBWIN-775: Removed Zoom proctoring implementation. 2023-11-01 10:42:26 +01:00
Damian Büchel
75016158c5 SEBWIN-732: Fixed unit tests for kiosk mode operation. 2023-11-01 10:25:54 +01:00
Damian Büchel
4ac982a3dd SEBWIN-772: Added user-specific cursor path to verification. 2023-11-01 09:23:37 +01:00
Damian Büchel
ca02b1d674 SEBWIN-734: Fixed status info in Action Center WLAN control when WLAN enabled but connected to wired network. 2023-11-01 09:22:24 +01:00
Damian Büchel
400b259af7 Updated solution dependencies and browser engine (version 117.2.40). 2023-10-20 16:55:54 +02:00
Damian Büchel
421b3db53e SEBWIN-763: Added Pulseway RMM to default list of prohibited applications. 2023-10-19 17:42:26 +02:00
Damian Büchel
2ef7c2c5ec SEBWIN-759, #606: Fixed bug in cursor path verification. 2023-10-11 15:50:17 +02:00
Damian Büchel
f7479cd1a8 SEBWIN-756: Improved logging with respect to default settings initialization. 2023-10-06 16:22:57 +02:00
Damian Büchel
c44bac79fd SEBWIN-732: Removed unnecessary nullable specification. 2023-10-04 16:43:37 +02:00
Damian Büchel
026d1fbfd8 SEBWIN-732: Implemented random desktop functionality. 2023-10-04 14:48:08 +02:00
Damian Büchel
3711555f70 SEBWIN-727: Implemented support for configuration data URIs. 2023-09-05 17:47:05 +02:00
Damian Büchel
bbfa720b21 SEBWIN-730: Added Splashtop to default list of prohibited applications. 2023-09-04 16:48:53 +02:00
Damian Büchel
722d84978c SEBWIN-714, #606: Implemented basic cursor functionality. Minor refactoring of registry and file system dialog classes. 2023-09-01 12:28:03 +02:00
Damian Büchel
fa16710bdb SEBWIN-612, #625: Implemented configuration options for clipboard policy. 2023-08-11 18:24:45 +02:00
Damian Büchel
d76dbf6b40 SEBWIN-717, #637: Fixed loading of MAC address for system info. 2023-07-31 15:19:31 +02:00
Damian Büchel
b36df9ad5a SEBWIN-717: Merged changes & minor code cleanup. 2023-07-31 10:52:40 +02:00
Damian Büchel
bd993ecc6b SEBWIN-717: Minor code cleanup. 2023-07-31 10:06:09 +02:00
Damian Büchel
fcebf4b436
Merge pull request #634 from Notselwyn/ProxmoxVMDetection
Extended ISystemInfo with CPU and removed unnecessary (debug) …
2023-07-28 16:26:26 +02:00
Damian Büchel
c498ef9af1 SEBWIN-716, #323: Fixed unit test failing due to runtime window change. 2023-07-25 15:11:55 +02:00
Damian Büchel
0769cf6b4b SEBWIN-716, #323: Fixed issue where password dialog was visible but not having input focus during application startup. 2023-07-25 14:14:05 +02:00
Notselwyn
56732537f8 chore: extended ISystemInfo with CPU and removed unnecessary (debug) logging 2023-07-22 14:19:42 +02:00
Damian Büchel
27f2fde904 SEBWIN-612, #625: Fixed broken unit tests. 2023-07-21 13:49:43 +02:00
Damian Büchel
44432ab023 Merge branch 'master' of https://github.com/SafeExamBrowser/seb-win-refactoring 2023-07-21 09:32:01 +02:00
Damian Büchel
eff0051469 SEBWIN-612, #625: Implemented basic clipboard functionality. 2023-07-21 09:31:59 +02:00
Damian Büchel
bd3b348f6a
Merge pull request #597 from Notselwyn/ProxmoxVMDetection
Proxmox vm detection
2023-07-18 16:33:55 +02:00
Notselwyn
9b0bfa291e chore: fixed Hungarian notation and corrected scanned registry docs 2023-07-18 15:20:10 +02:00
Notselwyn
5173bf3d6e chore: removed more unnecessary docs, and changed function names 2023-07-18 15:11:44 +02:00
Notselwyn
c1307624d9 chore: added function blocks and fixed if statement 2023-07-18 15:02:02 +02:00
Notselwyn
210a0419ca chore: removed unnecessary docs and changed var declarations 2023-07-18 14:45:10 +02:00
Notselwyn
f2917f69a6 chore: change varnames (and declarations), and fix registry bug 2023-07-18 14:32:54 +02:00
Notselwyn
bc30e56e38 chore: fix style issue (out var x) 2023-07-18 14:21:49 +02:00
Notselwyn
a21c9007ab fix: removed debug logging statements 2023-07-17 17:40:21 +02:00
Notselwyn
689e388e23 chore: split up functions and added docs 2023-07-17 17:33:21 +02:00
Notselwyn
3b8f552138 fix: ported first part of IsVirtualRegistry to use IRegistry 2023-07-17 17:06:46 +02:00
Notselwyn
e99bdabc51 fix: used default instead of null for better type safety 2023-07-17 16:57:14 +02:00
Notselwyn
7fc31f6e90 feat: extended IRegistry interface (no breaking changes). VM detection is broken regardless 2023-07-17 16:40:33 +02:00
Damian Büchel
940baae655 SEBWIN-679: Extended unit tests for core library. 2023-07-05 11:19:01 +02:00
Damian Büchel
8543c81867 SEBWIN-679: Extended unit tests for core library. 2023-07-05 09:42:34 +02:00
Damian Büchel
817f598d8a SEBWIN-679: Extended unit tests for core library and attempted to fix open windows test for external application. 2023-07-04 17:19:42 +02:00
Damian Büchel
37e3950a6f SEBWIN-643: Fixed exception due to missing check when loading version restrictions in configuration tool. 2023-07-04 11:23:57 +02:00
Damian Büchel
3dd023b285 SEBWIN-643: Implemented version restriction functionality. 2023-07-03 15:25:31 +02:00
Damian Büchel
543ad7040b Fixed unit test verifying open windows of external applications. 2023-06-23 10:40:57 +02:00
Damian Büchel
5284a52278 SEBWIN-693: Updated target framework to .NET Framework 4.8, changed setup bundle to also embed .NET setup bootstrapper and integrated handling of external setup bundle packages into build procedure as pre- and post-build events. 2023-06-22 16:15:23 +02:00
Damian Büchel
204db744aa SEBWIN-672: Improved error message for signature load error of third-party applications. 2023-06-06 12:20:02 +02:00
Damian Büchel
fd55367a7d SEBWIN-674: Improved unit test for third-party application logic. 2023-06-06 12:07:49 +02:00
Damian Büchel
11b10e8e45 SEBWIN-674: Extended unit tests for third-party application logic. 2023-06-02 15:51:45 +02:00
Damian Büchel
627c568400 SEBWIN-674: Extended unit test coverage for third-party application logic. 2023-06-02 14:08:11 +02:00
Damian Büchel
9507888900 SEBWIN-674: Extended unit test coverage for third-party application logic. 2023-06-01 18:18:01 +02:00
Damian Büchel
82908607e5 SEBWIN-674: Added SafeExamBrowser.Applications to unit test coverage collection of CI pipeline. 2023-05-31 13:56:07 +02:00
Damian Büchel
23dd94d23c SEBWIN-674: Added unit test assembly for third-party application logic. 2023-05-30 18:08:50 +02:00
Damian Büchel
3b8c63ab56 Merge branch 'master' of https://github.com/SafeExamBrowser/seb-win-refactoring 2023-05-30 15:28:23 +02:00
Damian Büchel
40a7f63524 SEBWIN-703: Added MSFT Virtual DVD device to whitelist. 2023-05-30 15:28:21 +02:00
Damian Büchel
16fa6a0473
Updated issue templates. 2023-05-24 17:32:22 +02:00
Damian Büchel
1605f0d00e SEBWIN-661: Fixed typo. 2023-05-22 18:18:47 +02:00
Damian Büchel
e3c26335a9 SEBWIN-661: Added Spanish translation. 2023-05-22 18:14:39 +02:00
Damian Büchel
989d414152 SEBWIN-588: Attempt to fix missing BEK and CK headers after e.g. authentication redirection. 2023-05-02 17:35:52 +02:00
Damian Büchel
e33a12e7ec SEBWIN-703, #604: Fixed false-positive VM detection due to virtual disk devices. 2023-05-02 14:56:15 +02:00
Damian Büchel
557e8a6be4 SEBWIN-672: Implemented basic signature verification for application monitoring. 2023-05-01 18:29:00 +02:00
Damian Büchel
ba128bb6ac SEBWIN-702: Fixed bug with quit URL where URLs not exactly matching the quit URL would also trigger a shutdown. 2023-04-26 15:05:23 +02:00
Notselwyn
e4e0f7c16b fix: corrected lowercasing on computercheck name 2023-04-14 21:22:53 +02:00
Notselwyn
efb3c8056a fix: fixed crash when env var "computername" does not exist 2023-04-14 21:18:08 +02:00
Notselwyn
c201389af4 chore: switches to using var instead of explicit typing 2023-04-14 20:13:29 +02:00
Notselwyn
538127661f chore: moved public functions above private functions 2023-04-14 19:56:22 +02:00
Damian Büchel
250ddb5bc9 Attempt #3 to fix codecov installation in CI test pipeline. 2023-04-14 16:59:20 +02:00
Damian Büchel
da609f62c8 Attempt #2 to fix test CI pipeline. 2023-04-14 16:42:40 +02:00
Damian Büchel
bf00b3883b Attempt to fix test CI pipeline. 2023-04-14 16:36:46 +02:00
Damian Büchel
390019048e Updated version to 3.6.0 beta. 2023-04-14 16:05:59 +02:00
user
22ef7ef364 chores 2023-04-08 12:53:06 +02:00
Damian Büchel
8f667d6efa Release build of version 3.5.0. 2023-04-03 15:36:26 +02:00
user
1fec696909 added computerIds check 2023-04-01 20:14:24 +02:00
user
bd145e14b0 chores 2023-04-01 19:54:38 +02:00
user
c0f37b309b added registry check for the device cache 2023-04-01 19:09:01 +02:00
user
71b722d215 added mac check, added WMI checks, and in progress registry check 2023-04-01 16:25:56 +02:00
Damian Büchel
b5008f9163 SEBWIN-670, #576: Fixed NullReferenceException when accessing CanNavigate properties of browser control. 2023-03-28 14:49:00 +02:00
Damian Büchel
eac87bfb87 SEBWIN-664: Added rule to ignore all contract assemblies for code coverage. 2023-03-28 09:38:35 +02:00
Damian Büchel
dab86a31f5 SEBWIN-668, #589: Fixed crash when using SEB without browser and pressing the Windows key. 2023-03-27 16:55:17 +02:00
Damian Büchel
65840f646e SEBWIN-667: Fixed crash when third-party application has no title. 2023-03-24 18:18:02 +01:00
Damian Büchel
4aa856f98a SEBWIN-645, SEBWIN-663: Reverted to informational build version for user agent of network resource requests. 2023-03-22 17:09:57 +01:00
Damian Büchel
cba73bd727 SEBWIN-645: Replaced informational with build version for user agent of network resource requests. 2023-03-15 20:44:24 +01:00
Damian Büchel
b2c2508812 Updated browser engine to version 111.2.20. 2023-03-15 20:22:24 +01:00
Damian Büchel
e0581620a0 Updated solution dependencies and browser engine (version 110.0.300). 2023-03-14 00:54:48 +01:00
Damian Büchel
7594a082fa SEBWIN-642, #548: Changed wording in settings password dialog. 2023-03-13 21:40:46 +01:00
Damian Büchel
836389942f Fixed bug where network status log would be sent repeatedly when not connected and added threshold for value change. 2023-03-13 18:13:42 +01:00
Damian Büchel
0cdffd891b SEBWIN-593, #431: Implemented new configuration option to show or hide the path of file system elements. 2023-03-09 22:49:54 +01:00
Damian Büchel
669b51d5ff SEBWIN-623, SEBWIN-628, SEBWIN-641, #521: Fixed hacks to control Zoom user interface. 2023-03-09 18:39:00 +01:00
Damian Büchel
b69280731a SEBWIN-634: Implemented custom browser exam key defined by server. 2023-03-08 22:24:29 +01:00
Damian Büchel
cdb08798b8 SEBWIN-649: Updated year in license and copyright remarks. 2023-03-08 00:30:20 +01:00
Damian Büchel
8c687e69a8 SEBWIN-640, #552: Only search for an LMS session identifier when a server session is active. 2023-03-08 00:01:20 +01:00
Damian Büchel
7967087ee6 SEBWIN-653: Added Hyper-V to virtual machine detector. 2023-03-07 23:41:56 +01:00
Damian Büchel
a5202e3a53 SEBWIN-650, #575: Added Chrome Remote Desktop to default list of prohibited applications. 2023-03-07 23:23:07 +01:00
Damian Büchel
66bd6a2d90 Merge branch 'master' of https://github.com/SafeExamBrowser/seb-win-refactoring 2023-03-07 23:13:02 +01:00
Damian Büchel
0bb9f42a3a 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. 2023-03-07 23:12:59 +01:00
Damian Büchel
510f127063
Merge pull request #578 from yolpsoftware/master
Various accessibility improvements
2023-03-06 19:10:04 +01:00
Damian Büchel
296f87727d SEBWIN-645: Added basic user agent for network resource requests. 2023-03-06 17:15:51 +01:00
Jonas Sourlier
298e8f34f4 fix possible IndexOutOfRangeException 2023-03-06 17:03:01 +01:00
Damian Büchel
e743d4a564 SEBWIN-608: Finished app signature key implementation. 2023-03-02 23:48:11 +01:00
Jonas Sourlier
3ca514e653 accessibility bugfixes 2023-02-28 15:26:48 +01:00
Jonas Sourlier
094ff4765b fix modal auto-close 2023-02-28 15:26:48 +01:00
Jonas Sourlier
5c5a70ad73 fix WLAN menu keyboard accessibility 2023-02-28 15:26:48 +01:00
Jonas Sourlier
1ad20567b8 fix AudioControl nested focusability 2023-02-28 15:26:48 +01:00
Jonas Sourlier
31f5c75a90 fix popups space bar problem 2023-02-28 15:26:48 +01:00
Jonas Sourlier
765bfcb516 network button accessibility 2023-02-28 15:26:48 +01:00
Jonas Sourlier
a0fb74a07e work on accessibility 2023-02-28 15:26:47 +01:00
Jonas Sourlier
14ef0a2b2a work on accessibility 2023-02-28 15:26:47 +01:00
Jonas Sourlier
900115d66c possible accessibility bugfix to make JAWS read the help texts 2023-02-28 15:26:38 +01:00
Jonas Sourlier
20a2c11927 set tooltip help text for JAWS 2023-02-28 15:25:30 +01:00
Jonas Sourlier
0b9299f88f fix overlay MouseLeave behavior 2023-02-28 15:25:30 +01:00
Damian Büchel
6c31ce0833 SEBWIN-608: Forgot to define two header values. 2023-02-24 21:39:39 +01:00
Damian Büchel
da458bcfb0 SEBWIN-608: Refactored server proxy by extracting request implementations. 2023-02-24 21:33:26 +01:00
Damian Büchel
ae3755df84 SEBWIN-608: Implemented basic mechanism for app signature key exchange. 2023-02-24 15:48:54 +01:00
Damian Büchel
718a4550e9 SEBWIN-648: Added VMware to PCI vendor blacklist. 2023-02-23 16:40:26 +01:00
Damian Büchel
4d67be099a SEBWIN-611: Fixed resizing issue with lockscreen. 2023-02-17 00:07:18 +01:00
Damian Büchel
474d766926 SEBWIN-611: Second attempt at fixing resizing issue. 2023-02-16 22:59:58 +01:00
Damian Büchel
f57771fda9 SEBWIN-611: Forgot to invoke lockscreen re-initialization on dispatcher. 2023-02-16 22:18:47 +01:00
Damian Büchel
2aaa74c7b0 SEBWIN-611: Attempt to correctly resize lockscreen on display resolution change. 2023-02-16 17:54:40 +01:00
Damian Büchel
baad469be6 SEBWIN-633: Attempt to completely remove ease of access option from Security Screen. 2023-02-08 19:40:32 +01:00
Damian Büchel
e2e5d5ade8 SEBWIN-633: Fixed bug where ease of access configuration monitoring would trigger even though the service component is active. 2022-12-22 17:21:11 +01:00
Damian Büchel
9b8d1fc3b2 SEBWIN-633: Minor refactoring. 2022-12-21 05:50:26 +01:00
Damian Büchel
f0aecb06d9 SEBWIN-633: Fixed ease of access exploit. 2022-12-21 05:37:03 +01:00
Damian Büchel
3bd786543d SEBWIN-607, #480, #499: Fixed access to potentially uninitialized DOM in accessibility JavaScript code. 2022-11-29 13:55:19 +01:00
Damian Büchel
f948463685 SEBWIN-623, #521: Updated Zoom Web SDK version. 2022-11-25 14:36:45 +01:00
Damian Büchel
ef84456311 SEBWIN-621: Fixed race condition happening with OAuth2 token renewal. 2022-11-25 14:32:58 +01:00
Damian Büchel
1c42434b9a SEBWIN-615: Implemented session integrity verification. 2022-11-24 14:50:25 +01:00
Damian Büchel
04bebdffb2 Updated solution dependencies and browser engine (version 107.1.90). 2022-11-18 17:38:17 +01:00
Damian Büchel
6912d0f162 Updated solution dependencies and browser engine (version 106.0.290). 2022-11-02 09:54:44 +01:00
Damian Büchel
7647923d9c Added missing translation for server failure dialog. 2022-10-28 09:41:51 +02:00
Damian Büchel
89401df6f6 Implemented visual indication for active applications in taskbar. 2022-10-27 13:06:49 +02:00
Damian Büchel
cd053e760e
Merge pull request #494 from yolpsoftware/bugfix/490
fix https://github.com/SafeExamBrowser/seb-win-refactoring/issues/490
2022-10-27 11:20:37 +02:00
Jonas Sourlier
878ac29fe0 combine if statements 2022-10-27 10:27:55 +02:00
Jonas Sourlier
bb0f679c6b fix https://github.com/SafeExamBrowser/seb-win-refactoring/issues/490 2022-10-25 13:14:59 +02:00
Damian Büchel
75907928d7 Updated solution dependencies and browser engine (version 106.0.260). 2022-10-10 15:42:02 +02:00
Damian Büchel
22e4e3fa7b SEBWIN-477: Ensured multiple lock screen instructions sent by the server don't cause multiple lock screens. 2022-09-02 15:00:51 +02:00
Damian Büchel
3a39784af2 SEBWIN-477: Minor refactoring. 2022-09-02 14:56:49 +02:00
Damian Büchel
5709f6a3dc Merge branch 'master' of https://github.com/SafeExamBrowser/seb-win-refactoring 2022-09-01 10:52:09 +02:00
Damian Büchel
1ec53d1e1d SEBWIN-599, #429: Ensured setup bundle doesn't fail if a newer WebView2 runtime is already installed. 2022-09-01 10:52:07 +02:00
anhefti
c13b2d2ac7 SEBWIN-477 implementation 2022-08-31 14:11:19 +02:00
Damian Büchel
72848d09af Updated solution dependencies and browser engine (version 104.4.240). 2022-08-31 12:12:32 +02:00
Damian Büchel
4bb46c0d7a SEBWIN-596: Fixed reverting for normal session. 2022-08-24 17:56:09 +02:00
Damian Büchel
cb3cee7e4e SEBWIN-596: Ensured open server connection gets closed when session start failed. 2022-08-24 10:25:41 +02:00
Damian Büchel
2d34ed30eb Added missing references to unit test libraries. 2022-08-23 16:01:45 +02:00
Damian Büchel
5237bbaa64 SEBWIN-594: Fixed crash when attempting to download file originating from data URL. 2022-08-23 11:00:47 +02:00
Damian Büchel
194eec131c SEBWIN-592, #421: Fixed unit tests for client operation. 2022-08-17 14:48:52 +02:00
Damian Büchel
3f2342a3d3 SEBWIN-592, #421: Fixed crash caused by non-ASCII characters (e.g. Hebrew) in client log file path. 2022-08-17 14:40:41 +02:00
Damian Büchel
561e14822d Updated version to 3.5.0 beta. 2022-08-09 15:45:09 +02:00
Damian Büchel
5129c2cb43 SEBWIN-587: Perform forward search on enter. 2022-08-09 15:37:50 +02:00
Damian Büchel
29547179c5 Release build of version 3.4.0. 2022-08-05 16:08:47 +02:00
Damian Büchel
255cc216b0 SEBWIN-578, #429: Removed exit code handling as exit code to handle is bigger than an integer and WIX only allows integers. 2022-07-29 16:35:32 +02:00
Damian Büchel
c876e6bbbd SEBWIN-578, #429: Ensured setup bundle doesn't fail if a newer WebView2 runtime is already installed. 2022-07-29 16:16:03 +02:00
Damian Büchel
20357c8e75 SEBWIN-577: Implemented detection of BIOS manufacturer & name. 2022-07-29 13:49:26 +02:00
Damian Büchel
a7bb5f543a Fixed usability issues with lock screen and password dialog. 2022-07-28 12:39:58 +02:00
Damian Büchel
6f9420ca8f SEBWIN-584, SEBWIN-585: Fixed unit test for PDF toolbar redirection. 2022-07-27 17:56:50 +02:00
Damian Büchel
2875eb4c94 SEBWIN-572: Improved stability of SEB Server connection by automatically updating OAuth2 token if it expires. 2022-07-27 15:26:42 +02:00
Damian Büchel
3277892ff2 Updated browser engine to version 103.0.120. 2022-07-26 20:33:40 +02:00
Damian Büchel
f5507cc2bc SEBWIN-584, SEBWIN-585: Fixed issues with internal PDF reader toolbar. 2022-07-26 17:56:40 +02:00
Damian Büchel
d2d93db9f0 SEBWIN-581: Implemented new registry setting to suppress find printer option in system print dialog. 2022-07-26 15:10:48 +02:00
Damian Büchel
32be808415 SEBWIN-581: Implemented new configuration option to control printing of web content. 2022-07-25 20:25:42 +02:00
Damian Büchel
9d7b89d36c SEBWIN-510, #278, #417: Fixed crash when attempting to execute JavaScript. 2022-07-25 18:37:43 +02:00
Damian Büchel
82e8166fd5 SEBWIN-510, #278, #417: Attempt to fix crashes on slow / virtual machines. 2022-07-25 15:21:26 +02:00
Damian Büchel
b4e0493b31 SEBWIN-510: Changed initialization of JavaScript API. 2022-07-22 15:38:07 +02:00
Damian Büchel
1f5efc748d Updated browser engine to version 103.0.90. 2022-07-22 14:21:32 +02:00
Damian Büchel
461e0a38b4 SEBWIN-510: Added remark about platform-specific BEK to configuration tool. 2022-07-21 15:59:42 +02:00
Damian Büchel
4d0f9797c6 SEBWIN-510: Implemented configuration tool changes. 2022-07-21 15:29:56 +02:00
Damian Büchel
20ff39493d SEBWIN-510: Made lazy initialization of BEK thread-safe. 2022-07-20 20:38:13 +02:00
Damian Büchel
d82d62f35f SEBWIN-510: Made initialization of SEB JavaScript API asynchronous in order to fix issue on slow / virtualized machines. 2022-07-20 19:54:44 +02:00
Damian Büchel
58ec2dde35 SEBWIN-510: Added safeguard against missing configuration key value. 2022-07-20 16:03:53 +02:00
Damian Büchel
a5029dbdd9 SEBWIN-510: Reverted attempt to fix interop issue. 2022-07-20 13:16:13 +02:00
Damian Büchel
b88e26b3ab SEBWIN-510: Attempt to fix interop issue. 2022-07-20 12:30:01 +02:00
Damian Büchel
3e18a6ce41 SEBWIN-510: Implemented build integration. 2022-07-19 12:07:38 +02:00
Damian Büchel
2fdacfc1b0 SEBWIN-510: Implemented functionality. 2022-07-18 21:37:04 +02:00
Damian Büchel
bd5f7d4293 Removed unnecessary async call. 2022-07-18 10:49:34 +02:00
Damian Büchel
4e49821644 Merge branch 'master' of https://github.com/SafeExamBrowser/seb-win-refactoring 2022-07-01 13:47:22 +02:00
Damian Büchel
a7351aa202 Updated solution dependencies and browser engine (version 102.0.10). 2022-07-01 13:47:20 +02:00
Damian Büchel
7f53b3ddf4
Merge pull request #417 from yolpsoftware/master
Accessibility round 2
2022-06-01 12:17:30 +02:00
Jonas Sourlier
fd2840a2a0 remove unnecessary project references 2022-06-01 12:07:17 +02:00
Jonas Sourlier
c2ffb0f15b remove whitespace 2022-06-01 12:07:17 +02:00
Jonas Sourlier
475c46529f accessibility bugfixes 2022-06-01 12:07:17 +02:00
Jonas Sourlier
57ab7cabcf work on accessibility 2022-06-01 12:07:17 +02:00
Jonas Sourlier
cb2859ff60 accessible Touch UI 2022-06-01 12:07:17 +02:00
Damian Büchel
406e766cf2 SEBWIN-513, #154: Disabled pre-join page for Jitsi Meet integration. 2022-05-31 14:37:30 +02:00
Damian Büchel
a4d29b2301 Updated browser engine to version 102.0.90. 2022-05-31 12:49:32 +02:00
Damian Büchel
66445a117f SEBWIN-568: Implemented functionality to automatically reperform text search on browser navigation or reload. 2022-05-31 12:34:01 +02:00
Damian Büchel
930c07d193 Removed browser reference from desktop UI. 2022-05-31 11:10:44 +02:00
Damian Büchel
fc1c22d902 Updated solution dependencies and browser engine (version 101.0.18). 2022-05-24 16:39:55 +02:00
Damian Büchel
dce2477a4b SEBWIN-556: Now sending OS and SEB version information to SEB Server as early as possible. 2022-05-18 09:17:08 +02:00
Damian Büchel
bfe4a32098 SEBWIN-554, SEBWIN-544: Changed and improved load error handling (resource request do not trigger error message anymore). 2022-05-13 16:47:18 +02:00
Damian Büchel
bc1583c070 SEBWIN-569: Added message when down- and uploading is not allowed. 2022-05-13 12:03:06 +02:00
Damian Büchel
ee852057ce SEBWIN-567: Ensured action center and taskbar keyboard activators do not block one another. 2022-05-11 10:39:03 +02:00
Damian Büchel
05a4bd126a Updated solution dependencies and browser engine (version 101.0.15). 2022-05-06 16:33:06 +02:00
Damian Büchel
aa6c765729 SEBWIN-567: Implemented keyboard activator for taskbar. 2022-05-06 15:52:37 +02:00
Damian Büchel
043187f4ec
Merge pull request #278 from yolpsoftware/master
Proof-of-concept desktop keyboard navigation
2022-05-03 19:33:32 +02:00
Jonas Sourlier
dbecaff451 add JavascriptResult 2022-05-03 12:53:07 +02:00
Jonas Sourlier
117ced0bf5 adapt accessibility changes to SEBWIN-531, #240 changes (7142380) 2022-05-03 11:24:54 +02:00
Jonas Sourlier
91b15eeb98 passing logger down to BrowserWindow 2022-05-03 09:36:09 +02:00
Jonas Sourlier
ef63a67aee add logger for javascript errors 2022-05-03 08:40:59 +02:00
Jonas Sourlier
ccbeb9d32d remove commented-out code 2022-05-02 17:07:43 +02:00
Jonas Sourlier
1ed747762a add deregistration of Browser.LoseFocusRequested 2022-05-02 16:51:18 +02:00
Jonas Sourlier
9cbb510c48 add TabPressedEventHandler.cs 2022-05-02 16:43:50 +02:00
Jonas Sourlier
235735fb3a add comment 2022-05-02 16:35:50 +02:00
Jonas Sourlier
cc37618da2 move LoseFocusRequestedEventHandler to Browser.Contracts 2022-05-02 16:33:35 +02:00
Jonas Sourlier
997385d95a text translations 2022-05-02 16:03:53 +02:00
Jonas Sourlier
d0676fa2db accessible power supply control 2022-05-02 15:44:31 +02:00
Jonas Sourlier
a4e56ead3d focus address bar by pressing Ctrl+L 2022-05-02 15:44:28 +02:00
Jonas Sourlier
ffe424725d accessible keyboard layout button 2022-05-02 15:42:59 +02:00
Jonas Sourlier
51c22b840c accessible Clock.xaml 2022-05-02 15:42:15 +02:00
Jonas Sourlier
ccbd478dfc German texts 2022-05-02 15:42:15 +02:00
Jonas Sourlier
8a19b205de added accessibility labels to UI controls 2022-05-02 15:42:15 +02:00
Jonas Sourlier
9d5792c7ec reload button accessibility 2022-05-02 15:42:15 +02:00
Jonas Sourlier
a7b6f9cb87 accessibility for audio and language controls 2022-05-02 15:42:11 +02:00
Jonas Sourlier
c783578bdf popup menu accessibility
(cherry picked from commit 2b6c2f0ecaa8953ae7f126b562e58b87f98d6d3f)
2022-05-02 15:40:19 +02:00
Jonas Sourlier
d040615c6e accessibility 2022-05-02 15:40:16 +02:00
Damian Büchel
a4d1904b81 SEBWIN-536, #349: Fixed keyboard system component to correctly display all installed keyboard layouts / input languages. 2022-04-26 16:45:53 +02:00
Damian Büchel
05430f6926 Updated solution dependencies and browser engine (to version 100.0.23). 2022-04-20 14:47:57 +02:00
Damian Büchel
6205bef251 SEBWIN-537: Implemented new network control showing wired as well as wireless network information. 2022-04-19 18:21:29 +02:00
Damian Büchel
3dda11956e SEBWIN-559, #378: Removed animated border of runtime window in attempt to fix performance issue in virtualized environments. 2022-03-31 11:25:20 +02:00
Damian Büchel
291c107fdd SEBWIN-558: Added Windows 11 to system info and supported OS list. 2022-03-28 21:04:31 +02:00
Damian Büchel
172845b3c0 SEBWIN-525: Forgot to set all display settings of permissive configuration for browser configuration resource. 2022-03-22 09:56:17 +01:00
Damian Büchel
ab73695d12
Changed naming for release build status to avoid misunderstandings. 2022-03-21 18:28:34 +01:00
Damian Büchel
d82b416667 Updated solution dependencies and browser engine (version 99.2.120). 2022-03-21 09:39:45 +01:00
Damian Büchel
fcbc641127 SEBWIN-482: Fixed unit tests. 2022-03-21 09:34:57 +01:00
Damian Büchel
a155c56198 SEBWIN-482: Translated new text keys. 2022-03-21 09:10:15 +01:00
anhefti
ab95995bf0 SEBWIN-482 do not allow to reconfigure new SEB Server session 2022-03-16 11:45:06 +01:00
Damian Büchel
01af8beedb SEBWIN-557: Fixed usage of unencrypted HTTP links. 2022-03-01 15:43:33 +01:00
Damian Büchel
eb0dbe0ab4 Merge branch 'master' of https://github.com/SafeExamBrowser/seb-win-refactoring 2022-02-23 13:59:41 +01:00
Damian Büchel
71423803e5 SEBWIN-531, #240: Replaced custom life span handler implementation with new API of the browser engine in order to enable parent-child relationship / JavaScript functionality for popup windows. 2022-02-23 13:59:36 +01:00
anhefti
5241def188 SEBWIN-482 fix for simple use-case (same exam selection) 2022-02-09 12:02:30 +01:00
Damian Büchel
83c387cffd SEBWIN-531, #240: Refactored browser application instance to browser window. 2022-02-04 13:41:11 +01:00
Damian Büchel
fa3763a32a Set C# language version to latest in CI build scripts. 2022-02-03 13:37:48 +01:00
Damian Büchel
1c3e4b450c SEBWIN-542: Ensured re-attempting to start a service session doesn't fail. 2022-02-03 12:15:54 +01:00
Damian Büchel
0d8d05166f Updated version to 3.4.0 beta. 2022-02-03 12:15:47 +01:00
Damian Büchel
c5e51ae2c1 Release build of version 3.3.2. 2022-01-28 16:13:47 +01:00
Damian Büchel
caf373bcc3 SEBWIN-541: Added WebEx to default list of prohibited applications. 2022-01-21 16:56:48 +01:00
Damian Büchel
a0e0c3e579 SEBWIN-541: Updated year in license and copyright remarks. 2022-01-21 16:33:52 +01:00
Damian Büchel
e8df5ad6a9 SEBWIN-523: Fixed issue with extraction of file icons for network file paths. 2022-01-18 22:29:01 +01:00
Damian Büchel
4444187cdd Updated browser engine to version 97.1.1. 2022-01-17 11:29:23 +01:00
Damian Büchel
3a5000a130 SEBWIN-540: Implemented transmission of client and version information to SEB-Server. 2022-01-14 13:33:35 +01:00
Damian Büchel
43f468dc68 SEBWIN-539: Fixed bug where a missing service component would lead to termination of SEB during reconfiguration. This due to the fact that the connection lost event of the service was erroneously still registered for the reconfiguration procedure. 2022-01-13 11:33:15 +01:00
Damian Büchel
87728f8301 SEBWIN-535: Fixed mapping of ping interval. 2022-01-06 14:35:34 +01:00
Damian Büchel
ab44eb6422 SEBWIN-538: Updated list of macOS versions in configuration tool. 2022-01-04 11:58:09 +01:00
Damian Büchel
a34bc6b9e9 SEBWIN-499: Improved label for restart instead of quitting configuration option. 2022-01-04 09:44:44 +01:00
Damian Büchel
5d5e15e522 SEBWIN-535: Implemented configuration mapping for ping interval of server proxy. 2022-01-03 17:38:15 +01:00
Damian Büchel
0abbef749e SEBWIN-533, #231: Improved probing of network configuration resources by always executing GET request in case HEAD fails. 2021-12-23 16:22:59 +01:00
Damian Büchel
aa88feb918 SEBWIN-519, #194: Forgot to log accept language value. 2021-12-23 15:23:54 +01:00
Damian Büchel
eb5f0e980d SEBWIN-519, #194: Set current language as default value for accept language list of browser. 2021-12-23 15:21:05 +01:00
Damian Büchel
4897d0ab6f SEBWIN-534, #261: Fixed bug where displays connected via eDP weren't recognized as internal displays. 2021-12-22 16:29:15 +01:00
Damian Büchel
e522a68ead SEBWIN-524: Fixed possible deadlock in graceful termination mechanism for kiosk mode Disable Explorer Shell. 2021-12-22 12:30:34 +01:00
Damian Büchel
9b092f98de Updated browser engine to version 96.0.180. 2021-12-20 12:32:09 +01:00
Damian Büchel
29a7681492 SEBWIN-508: Changed location to inject JavaScript API in order to fix issue with redirects. 2021-12-20 11:44:41 +01:00
Damian Büchel
804889301b SEBWIN-530: Updated Zoom client to latest version (2.0.1). 2021-11-15 11:24:08 +01:00
Damian Büchel
8180aaf962 SEBWIN-525: Added permissive default settings overrides for reconfiguration mechanism with authentication. 2021-11-12 18:29:24 +01:00
anhefti
c478b3b4a2 SEBWIN-487 added battery log event for power grid status 2021-11-10 12:02:01 +01:00
anhefti
34f25d51e8 Change to version 3.3.2 2021-11-10 11:13:32 +01:00
Damian Büchel
529c6bd7ac Updated browser engine to version 95.7.14. 2021-11-05 15:09:54 +01:00
Damian Büchel
db50ba22b8 Fixed typo. 2021-11-05 15:06:13 +01:00
Damian Büchel
4e7c597458 Updated solution dependencies and browser engine (version 94.4.11). 2021-10-28 10:02:43 +02:00
Damian Büchel
05db975785 SEBWIN-516: Added missing data mapping for raise hand feature and ensured that message panel is closed upon server confirmation. 2021-10-21 11:17:27 +02:00
Damian Büchel
0da587e521 SEBWIN-508: Implemented basic JavaScript API. 2021-10-18 12:06:10 +02:00
Damian Büchel
deccb3340c Updated solution dependencies and browser engine (version 94.4.50). 2021-10-06 14:37:51 +02:00
Damian Büchel
01187071c2 Updated version to 3.4.0 beta. 2021-10-06 10:56:37 +02:00
Damian Büchel
9da7c16718 SEBWIN-518: Fixed unit tests for new session identifier detection mechanism. 2021-10-05 10:58:17 +02:00
Damian Büchel
f11d4cb093 SEBWIN-518: Changed session identification to only trigger event when identifier changes. 2021-10-04 18:10:36 +02:00
Damian Büchel
55db0f8bf9 SEBWIN-516: Added icons for raise hand control and removed BETA version marker. 2021-10-04 11:00:30 +02:00
Damian Büchel
b5ec48498f SEBWIN-516: Disabled message input when hand is raised. 2021-09-20 14:53:22 +02:00
Damian Büchel
8a3039ec16 SEBWIN-516: Implemented raise hand feature. 2021-09-17 10:47:02 +02:00
Damian Büchel
bda36647f3 SEBWIN-515: Fixed Zoom implementation for version 1.9.8. 2021-09-02 11:17:32 +02:00
Damian Büchel
b5b5ad4feb Updated Zoom client API to version 1.9.8. 2021-08-31 18:18:54 +02:00
Damian Büchel
09141d44cc SEBWIN-514: Fixed issue with URL-encoded requests. 2021-08-31 18:15:26 +02:00
Damian Büchel
d6c4c69745 SEBWIN-514: Fixed bug leading to crash when mailto-URL is HTML encoded. 2021-08-24 15:23:17 +02:00
Damian Büchel
e14aa4b96c Updated browser engine to version 92.0.25. 2021-08-16 13:26:39 +02:00
Damian Büchel
ada64afe40 SEBWIN-506: Attempting to get bootstrapper download to work... 2021-08-16 13:17:03 +02:00
Damian Büchel
ef72e89508 SEBWIN-506: Attempting to get bootstrapper download to work... 2021-08-16 13:05:54 +02:00
Damian Büchel
cd74aea6c4 SEBWIN-506: Attempting to get bootstrapper download to work... 2021-08-16 12:50:41 +02:00
Damian Büchel
f5e1cf869d SEBWIN-506: Attempting to get bootstrapper download to work... 2021-08-16 12:40:22 +02:00
Damian Büchel
98e37bec59 SEBWIN-506: Attempting to get bootstrapper download to work... 2021-08-16 12:39:38 +02:00
Damian Büchel
69328af6f9 SEBWIN-506: Attempting to get bootstrapper download to work... 2021-08-16 12:33:42 +02:00
Damian Büchel
5b90491f61 SEBWIN-506: Attempting to get bootstrapper download to work... 2021-08-16 12:14:41 +02:00
Damian Büchel
d1ff36f493 SEBWIN-506: Attempting to get bootstrapper download to work... 2021-08-16 11:37:29 +02:00
Damian Büchel
c92f318a08 SEBWIN-506: Switched from remote payload to embedding the WebView2 bootstrapper. 2021-08-16 11:18:09 +02:00
Damian Büchel
40d2709371 SEBWIN-507: Ensured that the main browser window is always focused after initialization. 2021-08-13 14:33:12 +02:00
Damian Büchel
6b1aa69715 SEBWIN-504: Reverted ping interval to 1000ms. 2021-08-12 16:55:25 +02:00
Damian Büchel
d2cc87f12d Reverted version number to 3.3.1. 2021-08-12 15:39:19 +02:00
Damian Büchel
27d423489c SEBWIN-504: Increased server ping interval to 2000ms. 2021-08-10 17:36:51 +02:00
Damian Büchel
f2a1181049 SEBWIN-435: Improved icons in additional resource tab. 2021-08-09 14:15:06 +02:00
Damian Büchel
6d5ed6d1e5 SEBWIN-429: Removed zoom mode selection for Windows in legacy configuration tool. 2021-08-09 13:46:23 +02:00
Damian Büchel
66e7c50a8e SEBWIN-428: Fixed bug where visibility of reload button wasn't according to the active settings. 2021-08-09 12:29:11 +02:00
Damian Büchel
c6853f9239 SEBWIN-502: Implemented configuration option for middle mouse button. 2021-08-06 12:55:49 +02:00
Damian Büchel
0e13f4f3f1 SEBWIN-504: Attempt to improve server integration performance by using timer to send log events. 2021-08-03 14:56:58 +02:00
Damian Büchel
aab9e424fc Updated solution dependencies including browser engine (version 91.1.23). 2021-08-03 14:18:04 +02:00
Damian Büchel
53f9e3618e Updated version to 3.4.0 beta. 2021-08-03 14:13:00 +02:00
Damian Büchel
4195ee8309 Release build of version 3.3.0. 2021-08-03 11:50:54 +02:00
Damian Büchel
235992471c Updated WebView2 runtime dependency for setup bundle. 2021-08-03 11:32:22 +02:00
Damian Büchel
8b4d081c0f Extended unit tests for runtime component. 2021-07-30 18:38:41 +02:00
Damian Büchel
a0a577991f Extended unit tests for client component. 2021-07-27 13:52:49 +02:00
Damian Büchel
35726c99a7 Extended unit tests for communication proxies. 2021-07-26 11:31:49 +02:00
Damian Büchel
645ae7d307 Extended unit tests for client host. 2021-07-22 15:08:40 +02:00
Damian Büchel
66c28bd826 Extended unit tests for client component. 2021-07-22 14:21:06 +02:00
Damian Büchel
da2c709360 Extended unit tests for browser handlers. 2021-07-19 14:59:57 +02:00
Damian Büchel
8fb5519f4d SEBWIN-450: Added missing mapping for receive audio and video configuration values. 2021-07-14 11:59:06 +02:00
Damian Büchel
88faf4cafd SEBWIN-498: Made proctoring window bigger. 2021-07-14 11:50:03 +02:00
Damian Büchel
a791d49c1b #99: Enabled basic screen capturing. 2021-07-12 16:36:01 +02:00
Damian Büchel
f77181ab25 SEBWIN-450: Reverted change regarding window visibility. 2021-07-12 14:01:18 +02:00
Damian Büchel
c91292493b SEBWIN-450: Fixed issue with window visibility not being applied for server configurations. 2021-07-12 09:38:56 +02:00
Damian Büchel
bf851f2535 SEBWIN-496: Added Firefox and Ammyy Admin to the default list of prohibited applications. 2021-07-11 14:00:17 +02:00
Damian Büchel
316c384428 SEBWIN-475: Added asynchronous logout command via JavaScript again. 2021-07-08 14:18:15 +02:00
Damian Büchel
0a2b1670d4 SEBWIN-475: Moved leave instruction to window close event. 2021-07-07 11:31:55 +02:00
Damian Büchel
b67716b724 SEBWIN-475: Increased timeouts for Zoom proctoring integration and removed controls like mute and rename in participants overview. 2021-07-05 13:21:11 +02:00
Damian Büchel
a87e0c88a7 Updated WebView2 runtime dependency for setup bundle. 2021-07-02 18:13:16 +02:00
Damian Büchel
90d7712ae2 Updated solution dependencies and browser engine (version 91.1.211). 2021-07-02 12:08:10 +02:00
Damian Büchel
35b1a07c19 SEBWIN-484: Implemented temporary down- and upload folder for browser. 2021-07-01 18:54:43 +02:00
Damian Büchel
213f45ad3a SEBWIN-495: Improved error message for prohibited display configuration. 2021-06-29 17:34:05 +02:00
Damian Büchel
ee2133c0c2 SEBWIN-494: Fixed issue with sensitive proctoring data. 2021-06-29 09:44:16 +02:00
Damian Büchel
d141b747eb SEBWIN-475: Fixed issue with Zoom client not starting microphone and webcam. 2021-06-25 11:51:59 +02:00
Damian Büchel
f0b71ec10c SEBWIN-475: Implemented missing configuration options for Zoom proctoring. 2021-06-25 09:55:02 +02:00
Damian Büchel
2775abe172 SEBWIN-475: Ensured "More" button is not visible in Zoom proctoring UI. 2021-06-25 08:39:54 +02:00
Damian Büchel
d732e3ee4f SEBWIN-475: Fixed overriding of proctoring window visibility for Zoom. 2021-06-24 12:20:53 +02:00
Damian Büchel
bb06217187 SEBWIN-475: Ensured proctoring disclaimer is shown for server configurations as well. 2021-06-24 12:08:15 +02:00
Damian Büchel
cc8d8dddfc SEBWIN-485: Improved generic startup, session and shutdown error messages by adding all existing log file paths. 2021-06-23 10:44:39 +02:00
Damian Büchel
7b822b0278 Updated dependency in build script for test server. 2021-06-23 09:48:08 +02:00
Damian Büchel
dbeb4ba8ea Updated browser engine (version 91.1.21) and solution dependencies. 2021-06-23 09:46:03 +02:00
Damian Büchel
adb09b8a4a SEBWIN-486: Always automatically start server connectivity during client initialization. 2021-06-21 11:18:15 +02:00
Damian Büchel
da39fb1f59 SEBWIN-475: Implemented basic Zoom proctoring integration. 2021-06-19 21:04:13 +02:00
Damian Büchel
7ad1d6ae5d SEBWIN-475: Implemented basic proctoring with Zoom and SEB server. 2021-06-16 15:38:55 +02:00
Damian Büchel
cad8f21ff3 SEBWIN-475: Removed leave URL for Zoom meetings. 2021-06-10 12:52:07 +02:00
Damian Büchel
5e03997049 SEBWIN-475: Cleaned and updated Zoom proctoring implementation. 2021-06-09 19:37:21 +02:00
Damian Büchel
9252121b57 Updated OpenCover version in build script for test server. 2021-06-08 11:50:35 +02:00
Damian Büchel
8d7bde5d6c Updated browser engine (version 90.6.7) and solution dependencies. 2021-06-08 11:42:13 +02:00
Damian Büchel
d943af8828 Updated version to 3.3.0 BETA. 2021-06-08 11:36:21 +02:00
Damian Büchel
ce2f01775e Ordered runtime dependencies in readme. 2021-06-07 18:42:31 +02:00
Damian Büchel
c028d8d5d7 Added WebView2 to license remarks. 2021-06-07 18:15:11 +02:00
Damian Büchel
c3fd9660c4 Removed beta marker for version 3.2.0. 2021-06-07 13:21:55 +02:00
Damian Büchel
4ef5a3ace6 SEBWIN-463: Attempt to workaround issue with broken link navigation within PDFs. 2021-06-04 19:13:55 +02:00
Damian Büchel
e954ab09ef SEBWIN-491: Extended description of ignore failure option for display monitoring. 2021-06-01 13:21:40 +02:00
Damian Büchel
8ba5c80d86 SEBWIN-491: Fixed unit tests. 2021-06-01 13:07:55 +02:00
Damian Büchel
d804d50ac3 SEBWIN-491: Reverted default value for allowedDisplaysIgnoreFailure and added option to legacy configuration tool. 2021-06-01 12:59:07 +02:00
Damian Büchel
f4f4f10343 SEBWIN-491: Changed default value for allowedDisplaysIgnoreFailure and fixed log message on error. 2021-06-01 12:36:38 +02:00
Damian Büchel
ceb01441f5 SEBWIN-492: Fixed bug with auto-insertion of prohibited processes in legacy configuration tool. 2021-05-31 20:26:15 +02:00
Damian Büchel
025febc124 SEBWIN-492: Added default list of blacklisted applications. 2021-05-31 20:15:52 +02:00
Damian Büchel
d3bfe958aa SEBWIN-491: Implemented basic display configuration monitoring. 2021-05-30 20:04:44 +02:00
Damian Büchel
b456f0821f SEBWIN-449: Ensured server URL is also sanitized when not using SEB server and removed check for authentication token for Jitsi Meet. 2021-05-19 02:43:07 +02:00
Damian Büchel
fd2eab589c SEBWIN-490: Ensured users can't hangup call, automatically unmuting microphone and camera if users attempt to mute them, fixed window visibility on end of broadcast from SEB server, made initial proctoring view size bigger and fixed icon when proctoring is active. 2021-05-19 01:35:01 +02:00
Damian Büchel
ce625e4074 Updated version for OpenCover in build script for test server. 2021-05-12 18:02:41 +02:00
Damian Büchel
f2b66900c9 Updated browser engine and dependencies to latest version (CEF 90.6.5). 2021-05-12 17:26:10 +02:00
Damian Büchel
f8ffcd173a SEBWIN-449: Ensured participant leaves meeting on proctoring reconfiguration instruction. 2021-05-12 16:15:49 +02:00
Damian Büchel
590e152ea2 SEBWIN-450: Fixed issue with user data folder for WebView2. 2021-05-11 20:02:54 +02:00
Damian Büchel
2364b57f40 SEBWIN-488: Implemented pause before resetting or terminating browser in order to prevent application crashes. 2021-05-11 01:32:50 +02:00
Damian Büchel
7ad97b954c SEBWIN-489: Changed power supply thresholds from 40% = low and 20% = critical to 20% = low and 10% = critical. 2021-05-10 22:12:19 +02:00
Damian Büchel
8d6dc3b51b Updated WebView2 dependency. 2021-05-05 05:27:18 +02:00
Damian Büchel
22fa0e55a0 Added WebView2 runtime dependency to readme. 2021-04-26 15:00:11 +02:00
Damian Büchel
fbc9bead2d Updated WebView2 remote payload for setup bundle. 2021-04-26 14:49:06 +02:00
Damian Büchel
21dfed3727 Second attempt in fixing layout of setup bundle failure page. 2021-04-26 14:41:26 +02:00
Damian Büchel
b1436ea884 Attempt to fix UI issue on setup bundle failure page. 2021-04-26 14:25:44 +02:00
Damian Büchel
0f72ac7bc4 Fixed error in retry mechanism for SEB server. 2021-04-26 12:27:54 +02:00
Damian Büchel
0720698874 Disabled message regarding default processes. 2021-04-23 15:45:11 +02:00
Damian Büchel
c4a358e70c SEBWIN-471: Fixed unit test to ensure reconfiguration of secure session is not permitted without reconfiguring URL. 2021-04-23 15:34:12 +02:00
Damian Büchel
a33c7c0ff9 SEBWIN-483: Implemented generic method to retrieve LMS session identifier. 2021-04-21 19:53:52 +02:00
Damian Büchel
a9af0f41bc Changed default value for SEB Server timeout to 30 seconds. 2021-04-20 11:31:38 +02:00
Damian Büchel
20d25b50cd SEBWIN-466: Fixed issue where first letter wouldn't get registered in password fields of configuration tool. 2021-04-19 16:37:02 +02:00
Damian Büchel
65dd736822 SEBWIN-430: Changed wording for audio control setting. 2021-04-19 15:02:27 +02:00
Damian Büchel
e5b94c5cda SEBWIN-468: Fixed bug with zoom in keyboard command for English keyboard layout. 2021-04-19 12:45:37 +02:00
Damian Büchel
417a16ea49 SEBWIN-471: Fixed bug where reconfiguration URL wasn't mandatory in a secure session. 2021-04-19 10:49:02 +02:00
Damian Büchel
8187799929 SEBWIN-450: Disabled context menus, dev tools and status bar for proctoring view. 2021-04-18 17:37:35 +02:00
Damian Büchel
5d05acb6d7 SEBWIN-453: Implemented monitoring for kiosk mode Create New Desktop. 2021-04-16 19:12:56 +02:00
Damian Büchel
68f4349a4d Fixed URLs for request handler tests. 2021-04-16 19:05:46 +02:00
Damian Büchel
2d1d3d9dde Updated browser engine to latest version (89.0.17) and enabled WebRTC. 2021-04-16 10:47:10 +02:00
Damian Büchel
ee3ebfd3a9 SEBWIN-459: Added remark about required dependencies to setup. 2021-04-14 17:33:24 +02:00
Damian Büchel
222ce21070 SEBWIN-459: Extended error message of setup bundle regarding VC++ Redistributable. 2021-04-14 16:49:42 +02:00
Damian Büchel
62b72c85e1 SEBWIN-467: Fixed issue where the browser rendered configuration files instead of downloading them. 2021-04-13 17:12:09 +02:00
Damian Büchel
42c614cf24 SEBWIN-449: Improved replacement of scheme for server URL. 2021-04-13 16:57:11 +02:00
Damian Büchel
4286291877 SEBWIN-450: Added proctoring window for mobile UI. 2021-04-12 19:59:58 +02:00
Damian Büchel
0e120998b8 SEBWIN-448: Added text for remote proctoring. 2021-04-12 19:57:11 +02:00
Damian Büchel
8d5560d3c4 SEBWIN-451: Implemented dynamic reconfiguration for proctoring. 2021-04-12 15:59:42 +02:00
Damian Büchel
55603f3221 SEBWIN-450: Implemented static settings for proctoring with Jitsi Meet. 2021-04-12 10:59:31 +02:00
Damian Büchel
d309de2050 SEBWIN-474: Implemented workaround to always use kiosk mode Disable Explorer Shell when proctoring is active. 2021-03-29 20:05:17 +02:00
Damian Büchel
d4f9a9cc17 SEBWIN-448: Fixed mapping error for proctoring settings. 2021-03-26 15:55:13 +01:00
Damian Büchel
28ee48f433 SEBWIN-470: Changed display name of WebView2 runtime dependency. 2021-03-26 14:21:19 +01:00
Damian Büchel
8cea72d18b SEBWIN-470: Added WebView2 runtime bootstrapper to setup bundle. 2021-03-25 16:46:26 +01:00
Damian Büchel
ce67d4a475 SEBWIN-449: Extended notification mechanism to allow updating of notification icon / text and added icons for proctoring notification. 2021-03-25 13:49:45 +01:00
Damian Büchel
31a16caa87 SEBWIN-450: Completed basic implementation of proctoring window. 2021-03-23 21:12:47 +01:00
Damian Büchel
a3a5d42f98 Updated browser engine and dependencies to latest version (CEF 88.2.9). 2021-03-19 20:26:40 +01:00
Damian Büchel
4d4e2bf5f1 SEBWIN-449: Fixed pack scheme registration for unit tests. 2021-03-18 23:41:00 +01:00
Damian Büchel
48f9344164 SEBWIN-449: Refactored notification implementation, moved icon resources to core library (again) and removed client contracts. 2021-03-18 23:12:07 +01:00
Damian Büchel
f92c717e32 SEBWIN-449: Implemented workaround due to webcam and microphone access issue when using data URI for proctoring content. 2021-03-17 00:05:29 +01:00
Damian Büchel
52217fa477 SEBWIN-449: Implemented basic functionality of Jitsi Meet. 2021-03-10 21:26:45 +01:00
Damian Büchel
e72456b79e Duh. This happens when having to work on everything at the same time... :( 2021-03-04 17:27:20 +01:00
Damian Büchel
985f0a81f1 SEBWIN-469: Implemented workaround for user identifier retrieval via Moodle theme. 2021-03-04 17:25:19 +01:00
Damian Büchel
9f9f7c847d SEBWIN-449: Continuing work on remote proctoring. 2021-03-04 16:05:22 +01:00
Damian Büchel
bcb3dd602a SEBWIN-469: Implemented additional way to retrieve Moodle session identifier. 2021-02-23 15:32:08 +01:00
Damian Büchel
345c5b7b14 SEBWIN-449: Added WebView2 with scaffolding for remote proctoring UI. 2021-02-19 22:03:52 +01:00
Damian Büchel
d3b5df6180 SEBWIN-463: Ensured clicking a link with keyboard modifiers doesn't activate default browser behavior. 2021-02-15 23:47:34 +01:00
Damian Büchel
46fa25718b SEBWIN-458: Removed fix with reloading page as issue is within the browser engine itself. 2021-02-12 22:54:15 +01:00
Damian Büchel
584951752a SEBWIN-449: Added scaffolding for remote proctoring feature. 2021-02-10 23:21:48 +01:00
Damian Büchel
b3e5863538 SEBWIN-448: Changed title of proctoring disclaimer. 2021-02-10 22:27:11 +01:00
Damian Büchel
d113d59223 SEBWIN-448: Added configuration scaffolding for remote proctoring. 2021-02-10 00:49:32 +01:00
Damian Büchel
b27bf24eea SEBWIN-448: Implemented disclaimer for remote proctoring. 2021-02-09 23:07:09 +01:00
Damian Büchel
f4a00beebb SEBWIN-448: Started implementing remote proctoring disclaimer. 2021-02-09 14:44:59 +01:00
Damian Büchel
d48c333b6e SEBWIN-458: Ensured browser navigation keeps working when blocking a request. 2021-02-09 14:42:18 +01:00
Damian Büchel
c5c832d6c3 Updated browser engine and dependencies to latest version (CEF 87.1.13). 2021-02-06 04:55:15 +01:00
Damian Büchel
7ba6d9204c SEBWIN-458: Fixed issue with rendering of PDFs when request filtering is enabled. 2021-02-05 16:26:40 +01:00
Damian Büchel
fce97d9fca SEBWIN-456: Fixed input language switch issue. 2021-02-03 21:27:58 +01:00
Damian Büchel
a19617c53b Updated year in copyright remarks. 2021-02-03 00:45:33 +01:00
Damian Büchel
138e77f9c1 #63: Fixed issue with string marshalling when loading icons for file system dialog. 2020-12-21 11:31:22 +01:00
Damian Büchel
30c9c244f0 Updated version number to 3.2.0 BETA. 2020-12-21 11:29:24 +01:00
1160 changed files with 36339 additions and 8804 deletions

View file

@ -5,10 +5,131 @@ root = true
[*]
end_of_line = crlf
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
indent_size = 4
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_object_initializer = false:none
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_style_namespace_match_folder = true:suggestion
[*.cs]
dotnet_style_object_initializer = false:none
indent_style = tab
csharp_indent_labels = one_less_than_current
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_prefer_braces = true:silent
csharp_style_namespace_declarations = block_scoped:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_throw_expression = true:suggestion
csharp_style_prefer_null_check_over_type_check = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
csharp_style_prefer_tuple_swap = true:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
csharp_style_unused_value_assignment_preference = discard_variable:silent
csharp_prefer_static_local_function = true:suggestion
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
csharp_style_conditional_delegate_call = true:suggestion
csharp_style_prefer_switch_expression = true:suggestion
csharp_style_prefer_pattern_matching = true:silent
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_prefer_not_pattern = true:suggestion
csharp_style_prefer_extended_property_pattern = true:suggestion
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
csharp_space_after_cast = true
csharp_space_around_binary_operators = before_and_after
[*.xml]
indent_style = space
indent_style = space
[*.{cs,vb}]
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_readonly_field = true:suggestion
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
dotnet_style_allow_multiple_blank_lines_experimental = true:silent
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
dotnet_code_quality_unused_parameters = all:suggestion
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_property = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_event = false:silent

View file

@ -1,15 +1,15 @@
---
name: Bug Report
about: Create a report to help us improve Safe Exam Browser
about: Create a bug report to help us improve Safe Exam Browser.
title: ''
labels: ''
assignees: dbuechel
---
**IMPORTANT**
Please _always_ consult the documentation first before creating a bug report!
https://safeexambrowser.org/windows/win_usermanual_en.html
> [!IMPORTANT]
> - Please _always_ consult the documentation first before creating a bug report: https://safeexambrowser.org/windows/win_usermanual_en.html.
> - Please _always_ attach the log file(s) of the affected session(s)! They can be found under `%LocalAppData%\SafeExamBrowser\Logs`.
**Describe the Bug**
A clear and concise description of what the bug is.

View file

@ -1,6 +1,6 @@
---
name: Feature Request
about: Suggest an idea for Safe Exam Browser
about: Suggest an idea or new feature for Safe Exam Browser.
title: ''
labels: ''
assignees: dbuechel

54
.github/workflows/codeql.yml vendored Normal file
View file

@ -0,0 +1,54 @@
name: "CodeQL"
on:
push:
branches: [ "master", "*" ]
pull_request:
branches: [ "master", "*" ]
schedule:
- cron: '0 0 * * 1'
jobs:
analyze:
name: Analyze
runs-on: "windows-latest"
timeout-minutes: 360
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'csharp', 'javascript-typescript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

25
.github/workflows/issues.yml vendored Normal file
View file

@ -0,0 +1,25 @@
name: Issue Maintenance
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v8
with:
# https://github.com/marketplace/actions/close-stale-issues
days-before-issue-stale: 28
days-before-issue-close: 14
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 28 days with no activity. It will soon be closed automatically if there are no updates."
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
days-before-pr-stale: -1
days-before-pr-close: -1
exempt-issue-labels: "bug,enhancement,feature request,known issue"
repo-token: ${{ secrets.GITHUB_TOKEN }}

Binary file not shown.

View file

@ -2,25 +2,25 @@
Refactored version of Safe Exam Browser for Windows with Chromium as integrated browser engine.
### Requirements
## Requirements
SEB 3.x requires the prerequisites listed below in order to work correctly. These are automatically installed with the setup bundle and need only be manually installed when using the MSI packages.
* .NET Framework 4.7.2 Runtime: https://dotnet.microsoft.com/download/dotnet-framework/net472
* .NET Framework 4.8 Runtime: https://dotnet.microsoft.com/download/dotnet-framework/net48
* Visual C++ 2015-2019 Redistributable: https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads
### Project Status
## Project Status
**_DISCLAIMER_**\
**The builds linked below are for testing purposes only.** They may be unstable and should thus _never_ be used in a production environment! Always use the latest, official release version of SEB.
> [!WARNING]
> **The builds linked below are for testing purposes only.** They may be unstable and should thus _never_ be used in a production environment! Always use the latest, official release version of SEB.
| Aspect | Status | Details |
| --------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| Release Build | ![Release Build Status](https://sebdev-let.ethz.ch/api/projects/status/kq78qrjtnpk82ti0?svg=true) | https://sebdev-let.ethz.ch/project/appveyor/seb-win-refactoring |
| Test Build | ![Test Build Status](https://ci.appveyor.com/api/projects/status/a56akt9r174570m7?svg=true) | https://ci.appveyor.com/project/dbuechel/seb-win-refactoring |
| Test Run | ![AppVeyor Tests](https://img.shields.io/appveyor/tests/dbuechel/seb-win-refactoring?logo=appveyor&logoColor=%23ccc) | https://ci.appveyor.com/project/dbuechel/seb-win-refactoring |
| Code Coverage | ![Code Coverage](https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring/branch/master/graph/badge.svg) | https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring |
| Issue Status | ![GitHub Issues](https://img.shields.io/github/issues/safeexambrowser/seb-win-refactoring?logo=github) | https://github.com/SafeExamBrowser/seb-win-refactoring/issues |
| Downloads | ![GitHub All Releases](https://img.shields.io/github/downloads/safeexambrowser/seb-win-refactoring/total?logo=github) | https://github.com/SafeExamBrowser/seb-win-refactoring/releases |
| Development | ![GitHub Last Commit](https://img.shields.io/github/last-commit/safeexambrowser/seb-win-refactoring?logo=github) | n/a |
| Repository Size | ![GitHub Repo Size](https://img.shields.io/github/repo-size/safeexambrowser/seb-win-refactoring?logo=github) | n/a |
| Aspect | Status | Details |
| ----------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| Development Build | ![Development Build Status](https://sebdev.ethz.ch/api/projects/status/kq78qrjtnpk82ti0?svg=true) | https://sebdev.ethz.ch/project/appveyor/seb-win-refactoring |
| Test Build | ![Test Build Status](https://ci.appveyor.com/api/projects/status/a56akt9r174570m7?svg=true) | https://ci.appveyor.com/project/dbuechel/seb-win-refactoring |
| Test Run | ![AppVeyor Tests](https://img.shields.io/appveyor/tests/dbuechel/seb-win-refactoring?logo=appveyor&logoColor=%23ccc) | https://ci.appveyor.com/project/dbuechel/seb-win-refactoring |
| Code Coverage | ![Code Coverage](https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring/branch/master/graph/badge.svg) | https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring |
| Issue Status | ![GitHub Issues](https://img.shields.io/github/issues/safeexambrowser/seb-win-refactoring?logo=github) | https://github.com/SafeExamBrowser/seb-win-refactoring/issues |
| Downloads | ![GitHub All Releases](https://img.shields.io/github/downloads/safeexambrowser/seb-win-refactoring/total?logo=github) | https://github.com/SafeExamBrowser/seb-win-refactoring/releases |
| Development | ![GitHub Last Commit](https://img.shields.io/github/last-commit/safeexambrowser/seb-win-refactoring?logo=github) | n/a |
| Repository Size | ![GitHub Repo Size](https://img.shields.io/github/repo-size/safeexambrowser/seb-win-refactoring?logo=github) | n/a |

35
SECURITY.md Normal file
View file

@ -0,0 +1,35 @@
# Security Policy
We only support the latest official relese version with respect to security vulnerabilities. Thus, only the latest or then the upcoming next release version
will receive vulnerability fixes and security updates. A vulnerability may however be reported for any version, unless it already has been fixed with a later
release version.
## Reporting a Vulnerability
> [!IMPORTANT]
> - Please _always_ verify that no later release version exists which fixes the vulnerability.
> - Please _always_ consult the documentation first before creating a vulnerability report: https://safeexambrowser.org/windows/win_usermanual_en.html.
> - Please _always_ attach the log file(s) of the affected session(s)! They can be found under `%LocalAppData%\SafeExamBrowser\Logs`.
**Describe the Vulnerability**
A clear and concise description of what the vulnerability is.
**Steps to Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See ...
**Expected Behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Version Information**
- OS: [e.g. Windows 10 Professional, Version 1803]
- SEB-Version [e.g. SEB 3.0.1]
**Additional Context**
Add any other context about the vulnerability here.

View file

@ -1,12 +1,12 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
namespace SafeExamBrowser.Applications.Contracts.Events
{

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -9,14 +9,14 @@
using System;
using System.Collections.Generic;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
namespace SafeExamBrowser.Applications.Contracts
{
/// <summary>
/// Controls the lifetime and functionality of an application.
/// </summary>
public interface IApplication
public interface IApplication<out TWindow> where TWindow : IApplicationWindow
{
/// <summary>
/// Indicates whether the application should be automatically started.
@ -51,7 +51,7 @@ namespace SafeExamBrowser.Applications.Contracts
/// <summary>
/// Returns all windows of the application.
/// </summary>
IEnumerable<IApplicationWindow> GetWindows();
IEnumerable<TWindow> GetWindows();
/// <summary>
/// Performs any initialization work, if necessary.

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -18,6 +18,6 @@ namespace SafeExamBrowser.Applications.Contracts
/// <summary>
/// Attempts to create an application according to the given settings.
/// </summary>
FactoryResult TryCreate(WhitelistApplication settings, out IApplication application);
FactoryResult TryCreate(WhitelistApplication settings, out IApplication<IApplicationWindow> application);
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -8,12 +8,12 @@
using System;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
namespace SafeExamBrowser.Applications.Contracts
{
/// <summary>
/// Defines a window of an <see cref="IApplication"/>.
/// Defines a window of an <see cref="IApplication{TWindow}"/>.
/// </summary>
public interface IApplicationWindow
{

View file

@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Applications.Contracts")]
[assembly: AssemblyCopyright("Copyright © 2020 ETH Zürich, Educational Development and Technology (LET)")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from

View file

@ -9,9 +9,10 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.Applications.Contracts</RootNamespace>
<AssemblyName>SafeExamBrowser.Applications.Contracts</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
@ -61,14 +62,13 @@
<Compile Include="IApplication.cs" />
<Compile Include="IApplicationFactory.cs" />
<Compile Include="IApplicationWindow.cs" />
<Compile Include="Resources\Icons\BitmapIconResource.cs" />
<Compile Include="Resources\Icons\EmbeddedIconResource.cs" />
<Compile Include="Resources\Icons\IconResource.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Resources\Icons\NativeIconResource.cs" />
<Compile Include="Resources\Icons\XamlIconResource.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SafeExamBrowser.Core.Contracts\SafeExamBrowser.Core.Contracts.csproj">
<Project>{fe0e1224-b447-4b14-81e7-ed7d84822aa0}</Project>
<Name>SafeExamBrowser.Core.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
<Name>SafeExamBrowser.Settings</Name>

View file

@ -0,0 +1,126 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Settings.Applications;
using SafeExamBrowser.SystemComponents.Contracts.Registry;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications.UnitTests
{
[TestClass]
public class ApplicationFactoryTests
{
private Mock<IApplicationMonitor> applicationMonitor;
private Mock<IModuleLogger> logger;
private Mock<INativeMethods> nativeMethods;
private Mock<IProcessFactory> processFactory;
private Mock<IRegistry> registry;
private ApplicationFactory sut;
[TestInitialize]
public void Initialize()
{
applicationMonitor = new Mock<IApplicationMonitor>();
logger = new Mock<IModuleLogger>();
nativeMethods = new Mock<INativeMethods>();
processFactory = new Mock<IProcessFactory>();
registry = new Mock<IRegistry>();
sut = new ApplicationFactory(applicationMonitor.Object, logger.Object, nativeMethods.Object, processFactory.Object, registry.Object);
}
[TestMethod]
public void MustCorrectlyCreateApplication()
{
var settings = new WhitelistApplication
{
DisplayName = "Windows Command Prompt",
ExecutableName = "cmd.exe",
};
var result = sut.TryCreate(settings, out var application);
Assert.AreEqual(FactoryResult.Success, result);
Assert.IsNotNull(application);
Assert.IsInstanceOfType<ExternalApplication>(application);
}
[TestMethod]
public void MustCorrectlyReadPathFromRegistry()
{
object o = @"C:\Some\Registry\Path";
var settings = new WhitelistApplication
{
DisplayName = "Windows Command Prompt",
ExecutableName = "cmd.exe",
ExecutablePath = @"C:\Some\Path"
};
registry.Setup(r => r.TryRead(It.Is<string>(s => s.Contains(RegistryValue.MachineHive.AppPaths_Key)), It.Is<string>(s => s == "Path"), out o)).Returns(true);
var result = sut.TryCreate(settings, out var application);
registry.Verify(r => r.TryRead(It.Is<string>(s => s.Contains(RegistryValue.MachineHive.AppPaths_Key)), It.Is<string>(s => s == "Path"), out o), Times.Once);
Assert.AreEqual(FactoryResult.Success, result);
Assert.IsNotNull(application);
Assert.IsInstanceOfType<ExternalApplication>(application);
}
[TestMethod]
public void MustIndicateIfApplicationNotFound()
{
var settings = new WhitelistApplication
{
ExecutableName = "some_random_application_which_does_not_exist_on_a_normal_system.exe",
ExecutablePath = "Some/Path/Which/Does/Not/Exist"
};
var result = sut.TryCreate(settings, out var application);
Assert.AreEqual(FactoryResult.NotFound, result);
Assert.IsNull(application);
}
[TestMethod]
public void MustFailGracefullyWhenPathIsInvalid()
{
var settings = new WhitelistApplication
{
ExecutableName = "asdfg(/ç)&=%\"fsdg..exe..",
ExecutablePath = "[]#°§¬#°¢@tu03450'w89tz!$£äöüèé:"
};
var result = sut.TryCreate(settings, out _);
logger.Verify(l => l.Error(It.IsAny<string>(), It.IsAny<Exception>()), Times.AtLeastOnce);
Assert.AreEqual(FactoryResult.NotFound, result);
}
[TestMethod]
public void MustFailGracefullyAndIndicateThatErrorOccurred()
{
var o = default(object);
var settings = new WhitelistApplication();
registry.Setup(r => r.TryRead(It.IsAny<string>(), It.IsAny<string>(), out o)).Throws<Exception>();
var result = sut.TryCreate(settings, out var application);
Assert.AreEqual(FactoryResult.Error, result);
}
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications.UnitTests
{
[TestClass]
public class ExternalApplicationInstanceTests
{
private NativeIconResource icon;
private Mock<ILogger> logger;
private Mock<INativeMethods> nativeMethods;
private Mock<IProcess> process;
private ExternalApplicationInstance sut;
[TestInitialize]
public void Initialize()
{
icon = new NativeIconResource();
logger = new Mock<ILogger>();
nativeMethods = new Mock<INativeMethods>();
process = new Mock<IProcess>();
sut = new ExternalApplicationInstance(icon, logger.Object, nativeMethods.Object, process.Object, 1);
}
[TestMethod]
public void Terminate_MustDoNothingIfAlreadyTerminated()
{
process.SetupGet(p => p.HasTerminated).Returns(true);
sut.Terminate();
process.Verify(p => p.TryClose(It.IsAny<int>()), Times.Never());
process.Verify(p => p.TryKill(It.IsAny<int>()), Times.Never());
}
[TestMethod]
public void Terminate_MustLogIfTerminationFailed()
{
process.Setup(p => p.TryClose(It.IsAny<int>())).Returns(false);
process.Setup(p => p.TryKill(It.IsAny<int>())).Returns(false);
process.SetupGet(p => p.HasTerminated).Returns(false);
sut.Terminate();
logger.Verify(l => l.Warn(It.IsAny<string>()), Times.AtLeastOnce);
process.Verify(p => p.TryClose(It.IsAny<int>()), Times.AtLeastOnce());
process.Verify(p => p.TryKill(It.IsAny<int>()), Times.AtLeastOnce());
}
}
}

View file

@ -0,0 +1,216 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Monitoring.Contracts.Applications.Events;
using SafeExamBrowser.Settings.Applications;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications.UnitTests
{
[TestClass]
public class ExternalApplicationTests
{
private Mock<IApplicationMonitor> applicationMonitor;
private string executablePath;
private Mock<IModuleLogger> logger;
private Mock<INativeMethods> nativeMethods;
private Mock<IProcessFactory> processFactory;
private WhitelistApplication settings;
private ExternalApplication sut;
[TestInitialize]
public void Initialize()
{
applicationMonitor = new Mock<IApplicationMonitor>();
executablePath = @"C:\Some\Random\Path\Application.exe";
logger = new Mock<IModuleLogger>();
nativeMethods = new Mock<INativeMethods>();
processFactory = new Mock<IProcessFactory>();
settings = new WhitelistApplication();
logger.Setup(l => l.CloneFor(It.IsAny<string>())).Returns(new Mock<IModuleLogger>().Object);
sut = new ExternalApplication(applicationMonitor.Object, executablePath, logger.Object, nativeMethods.Object, processFactory.Object, settings, 1);
}
[TestMethod]
public void GetWindows_MustCorrectlyReturnOpenWindows()
{
var openWindows = new List<IntPtr> { new IntPtr(123), new IntPtr(234), new IntPtr(456), new IntPtr(345), new IntPtr(567), new IntPtr(789) };
var process1 = new Mock<IProcess>();
var process2 = new Mock<IProcess>();
var sync = new AutoResetEvent(false);
nativeMethods.Setup(n => n.GetOpenWindows()).Returns(openWindows);
nativeMethods.Setup(n => n.GetProcessIdFor(It.Is<IntPtr>(p => p == new IntPtr(234)))).Returns(1234);
nativeMethods.Setup(n => n.GetProcessIdFor(It.Is<IntPtr>(p => p == new IntPtr(345)))).Returns(1234);
nativeMethods.Setup(n => n.GetProcessIdFor(It.Is<IntPtr>(p => p == new IntPtr(567)))).Returns(5678);
process1.Setup(p => p.TryClose(It.IsAny<int>())).Returns(false);
process1.Setup(p => p.TryKill(It.IsAny<int>())).Returns(true);
process1.SetupGet(p => p.Id).Returns(1234);
process2.Setup(p => p.TryClose(It.IsAny<int>())).Returns(true);
process2.SetupGet(p => p.Id).Returns(5678);
processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process1.Object);
sut.WindowsChanged += () => sync.Set();
sut.Initialize();
sut.Start();
applicationMonitor.Raise(m => m.InstanceStarted += null, sut.Id, process2.Object);
sync.WaitOne();
sync.WaitOne();
var windows = sut.GetWindows();
Assert.AreEqual(3, windows.Count());
Assert.IsTrue(windows.Any(w => w.Handle == new IntPtr(234)));
Assert.IsTrue(windows.Any(w => w.Handle == new IntPtr(345)));
Assert.IsTrue(windows.Any(w => w.Handle == new IntPtr(567)));
nativeMethods.Setup(n => n.GetOpenWindows()).Returns(openWindows.Skip(2));
Task.Run(() => process2.Raise(p => p.Terminated += null, default(int)));
sync.WaitOne();
sync.WaitOne();
windows = sut.GetWindows();
Assert.AreEqual(1, windows.Count());
Assert.IsTrue(windows.Any(w => w.Handle != new IntPtr(234)));
Assert.IsTrue(windows.Any(w => w.Handle == new IntPtr(345)));
Assert.IsTrue(windows.All(w => w.Handle != new IntPtr(567)));
}
[TestMethod]
public void Initialize_MustInitializeCorrectly()
{
settings.AutoStart = new Random().Next(2) == 1;
settings.Description = "Some Description";
sut.Initialize();
applicationMonitor.VerifyAdd(a => a.InstanceStarted += It.IsAny<InstanceStartedEventHandler>(), Times.Once);
Assert.AreEqual(settings.AutoStart, sut.AutoStart);
Assert.AreEqual(executablePath, (sut.Icon as EmbeddedIconResource).FilePath);
Assert.AreEqual(settings.Id, settings.Id);
Assert.AreEqual(settings.DisplayName, sut.Name);
Assert.AreEqual(settings.Description ?? settings.DisplayName, sut.Tooltip);
}
[TestMethod]
public void Start_MustCreateInstanceCorrectly()
{
settings.Arguments.Add("some_parameter");
settings.Arguments.Add("another_parameter");
settings.Arguments.Add("yet another parameter");
sut.Start();
processFactory.Verify(f => f.StartNew(executablePath, It.Is<string[]>(args => args.All(a => settings.Arguments.Contains(a)))), Times.Once);
}
[TestMethod]
public void Start_MustHandleFailureGracefully()
{
processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Throws<Exception>();
sut.Start();
logger.Verify(l => l.Error(It.IsAny<string>(), It.IsAny<Exception>()), Times.AtLeastOnce);
processFactory.Verify(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>()), Times.Once);
}
[TestMethod]
public void Start_MustRemoveInstanceCorrectlyWhenTerminated()
{
var eventCount = 0;
var openWindows = new List<IntPtr> { new IntPtr(123), new IntPtr(234), new IntPtr(456), new IntPtr(345), new IntPtr(567), new IntPtr(789), };
var process = new Mock<IProcess>();
var sync = new AutoResetEvent(false);
nativeMethods.Setup(n => n.GetOpenWindows()).Returns(openWindows);
nativeMethods.Setup(n => n.GetProcessIdFor(It.Is<IntPtr>(p => p == new IntPtr(234)))).Returns(1234);
process.Setup(p => p.TryClose(It.IsAny<int>())).Returns(false);
process.Setup(p => p.TryKill(It.IsAny<int>())).Returns(true);
process.SetupGet(p => p.Id).Returns(1234);
processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process.Object);
sut.WindowsChanged += () =>
{
eventCount++;
sync.Set();
};
sut.Initialize();
sut.Start();
sync.WaitOne();
Assert.AreEqual(1, sut.GetWindows().Count());
process.Raise(p => p.Terminated += null, default(int));
Assert.AreEqual(2, eventCount);
Assert.AreEqual(0, sut.GetWindows().Count());
}
[TestMethod]
public void Terminate_MustStopAllInstancesCorrectly()
{
var process1 = new Mock<IProcess>();
var process2 = new Mock<IProcess>();
process1.Setup(p => p.TryClose(It.IsAny<int>())).Returns(false);
process1.Setup(p => p.TryKill(It.IsAny<int>())).Returns(true);
process1.SetupGet(p => p.Id).Returns(1234);
process2.Setup(p => p.TryClose(It.IsAny<int>())).Returns(true);
process2.SetupGet(p => p.Id).Returns(5678);
processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process1.Object);
sut.Initialize();
sut.Start();
applicationMonitor.Raise(m => m.InstanceStarted += null, sut.Id, process2.Object);
sut.Terminate();
process1.Verify(p => p.TryClose(It.IsAny<int>()), Times.AtLeastOnce);
process1.Verify(p => p.TryKill(It.IsAny<int>()), Times.Once);
process2.Verify(p => p.TryClose(It.IsAny<int>()), Times.Once);
process2.Verify(p => p.TryKill(It.IsAny<int>()), Times.Never);
}
[TestMethod]
public void Terminate_MustHandleFailureGracefully()
{
var process = new Mock<IProcess>();
process.Setup(p => p.TryClose(It.IsAny<int>())).Throws<Exception>();
processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process.Object);
sut.Initialize();
sut.Start();
sut.Terminate();
process.Verify(p => p.TryClose(It.IsAny<int>()), Times.AtLeastOnce);
process.Verify(p => p.TryKill(It.IsAny<int>()), Times.Never);
}
}
}

View file

@ -0,0 +1,64 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications.UnitTests
{
[TestClass]
public class ExternalApplicationWindowTests
{
private IntPtr handle;
private NativeIconResource icon;
private Mock<INativeMethods> nativeMethods;
private ExternalApplicationWindow sut;
[TestInitialize]
public void Initialize()
{
handle = new IntPtr(123);
icon = new NativeIconResource();
nativeMethods = new Mock<INativeMethods>();
sut = new ExternalApplicationWindow(icon, nativeMethods.Object, handle);
}
[TestMethod]
public void Activate_MustCorrectlyActivateWindow()
{
sut.Activate();
nativeMethods.Verify(n => n.ActivateWindow(It.Is<IntPtr>(h => h == handle)));
}
[TestMethod]
public void Update_MustCorrectlyUpdateWindow()
{
var iconChanged = false;
var titleChanged = false;
nativeMethods.Setup(m => m.GetWindowIcon(It.IsAny<IntPtr>())).Returns(new IntPtr(456));
nativeMethods.Setup(m => m.GetWindowTitle((It.IsAny<IntPtr>()))).Returns("Some New Window Title");
sut.IconChanged += (_) => iconChanged = true;
sut.TitleChanged += (_) => titleChanged = true;
sut.Update();
nativeMethods.Verify(m => m.GetWindowIcon(handle), Times.Once);
nativeMethods.Verify(m => m.GetWindowTitle(handle), Times.Once);
Assert.IsTrue(iconChanged);
Assert.IsTrue(titleChanged);
}
}
}

View file

@ -0,0 +1,16 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("SafeExamBrowser.Applications.UnitTests")]
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Applications.UnitTests")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
[assembly: ComVisible(false)]
[assembly: Guid("fc6d80ec-8611-4287-87e2-17c028a10858")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0.0")]

View file

@ -0,0 +1,199 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props')" />
<Import Project="..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props" Condition="Exists('..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props')" />
<Import Project="..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props" Condition="Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{FC6D80EC-8611-4287-87E2-17C028A10858}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.Applications.UnitTests</RootNamespace>
<AssemblyName>SafeExamBrowser.Applications.UnitTests</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<ItemGroup>
<Reference Include="Castle.Core, Version=5.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\packages\Castle.Core.5.1.1\lib\net462\Castle.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ApplicationInsights, Version=2.22.0.997, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.2.22.0\lib\net46\Microsoft.ApplicationInsights.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Testing.Extensions.Telemetry, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Extensions.TrxReport.Abstractions, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.TrxReport.Abstractions.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.TrxReport.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Extensions.VSTestBridge, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.VSTestBridge.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.VSTestBridge.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Platform, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\lib\netstandard2.0\Microsoft.Testing.Platform.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Platform.MSBuild, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\lib\netstandard2.0\Microsoft.Testing.Platform.MSBuild.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TestPlatform.CoreUtilities, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.TestPlatform.CoreUtilities.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TestPlatform.PlatformAbstractions, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.TestPlatform.PlatformAbstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.ObjectModel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.3.2.2\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.3.2.2\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
</Reference>
<Reference Include="Moq, Version=4.20.70.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.20.70\lib\net462\Moq.dll</HintPath>
</Reference>
<Reference Include="NuGet.Frameworks, Version=6.9.1.3, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\NuGet.Frameworks.6.9.1\lib\net472\NuGet.Frameworks.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Immutable, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.8.0.0\lib\net462\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Diagnostics.DiagnosticSource, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Diagnostics.DiagnosticSource.8.0.0\lib\net462\System.Diagnostics.DiagnosticSource.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http" />
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Metadata, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reflection.Metadata.8.0.0\lib\net462\System.Reflection.Metadata.dll</HintPath>
</Reference>
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ApplicationFactoryTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ExternalApplicationTests.cs" />
<Compile Include="ExternalApplicationWindowTests.cs" />
<Compile Include="ExternalApplicationInstanceTests.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SafeExamBrowser.Applications.Contracts\SafeExamBrowser.Applications.Contracts.csproj">
<Project>{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}</Project>
<Name>SafeExamBrowser.Applications.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Applications\SafeExamBrowser.Applications.csproj">
<Project>{a113e68f-1209-4689-981a-15c554b2df4e}</Project>
<Name>SafeExamBrowser.Applications</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Core.Contracts\SafeExamBrowser.Core.Contracts.csproj">
<Project>{fe0e1224-b447-4b14-81e7-ed7d84822aa0}</Project>
<Name>SafeExamBrowser.Core.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Logging.Contracts\SafeExamBrowser.Logging.Contracts.csproj">
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
<Name>SafeExamBrowser.Logging.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Monitoring.Contracts\SafeExamBrowser.Monitoring.Contracts.csproj">
<Project>{6d563a30-366d-4c35-815b-2c9e6872278b}</Project>
<Name>SafeExamBrowser.Monitoring.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
<Name>SafeExamBrowser.Settings</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.SystemComponents.Contracts\SafeExamBrowser.SystemComponents.Contracts.csproj">
<Project>{903129c6-e236-493b-9ad6-c6a57f647a3a}</Project>
<Name>SafeExamBrowser.SystemComponents.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.WindowsApi.Contracts\SafeExamBrowser.WindowsApi.Contracts.csproj">
<Project>{7016f080-9aa5-41b2-a225-385ad877c171}</Project>
<Name>SafeExamBrowser.WindowsApi.Contracts</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets'))" />
</Target>
<Import Project="..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets" Condition="Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets')" />
<Import Project="..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets')" />
</Project>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="NuGet.Frameworks" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.11.3.1" newVersion="5.11.3.1" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.ApplicationInsights" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.22.0.997" newVersion="2.22.0.997" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Metadata" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" /></startup></configuration>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Castle.Core" version="5.1.1" targetFramework="net48" />
<package id="Microsoft.ApplicationInsights" version="2.22.0" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.Telemetry" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.TrxReport.Abstractions" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.VSTestBridge" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Platform" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Platform.MSBuild" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.TestPlatform.ObjectModel" version="17.9.0" targetFramework="net48" />
<package id="Moq" version="4.20.70" targetFramework="net48" />
<package id="MSTest.TestAdapter" version="3.2.2" targetFramework="net48" />
<package id="MSTest.TestFramework" version="3.2.2" targetFramework="net48" />
<package id="NuGet.Frameworks" version="6.9.1" targetFramework="net48" />
<package id="System.Buffers" version="4.5.1" targetFramework="net48" />
<package id="System.Collections.Immutable" version="8.0.0" targetFramework="net48" />
<package id="System.Diagnostics.DiagnosticSource" version="8.0.0" targetFramework="net48" />
<package id="System.Memory" version="4.5.5" targetFramework="net48" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net48" />
<package id="System.Reflection.Metadata" version="8.0.0" targetFramework="net48" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net48" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net48" />
</packages>

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -9,39 +9,42 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Win32;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Settings.Applications;
using SafeExamBrowser.SystemComponents.Contracts.Registry;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications
{
public class ApplicationFactory : IApplicationFactory
{
private IApplicationMonitor applicationMonitor;
private IModuleLogger logger;
private INativeMethods nativeMethods;
private IProcessFactory processFactory;
private readonly IApplicationMonitor applicationMonitor;
private readonly IModuleLogger logger;
private readonly INativeMethods nativeMethods;
private readonly IProcessFactory processFactory;
private readonly IRegistry registry;
public ApplicationFactory(
IApplicationMonitor applicationMonitor,
IModuleLogger logger,
INativeMethods nativeMethods,
IProcessFactory processFactory)
IProcessFactory processFactory,
IRegistry registry)
{
this.applicationMonitor = applicationMonitor;
this.logger = logger;
this.nativeMethods = nativeMethods;
this.processFactory = processFactory;
this.registry = registry;
}
public FactoryResult TryCreate(WhitelistApplication settings, out IApplication application)
public FactoryResult TryCreate(WhitelistApplication settings, out IApplication<IApplicationWindow> application)
{
var name = $"'{settings.DisplayName}' ({ settings.ExecutableName})";
var name = $"'{settings.DisplayName}' ({settings.ExecutableName})";
application = default(IApplication);
application = default;
try
{
@ -69,10 +72,12 @@ namespace SafeExamBrowser.Applications
return FactoryResult.Error;
}
private IApplication BuildApplication(string executablePath, WhitelistApplication settings)
private IApplication<IApplicationWindow> BuildApplication(string executablePath, WhitelistApplication settings)
{
const int ONE_SECOND = 1000;
var applicationLogger = logger.CloneFor(settings.DisplayName);
var application = new ExternalApplication(applicationMonitor, executablePath, applicationLogger, nativeMethods, processFactory, settings);
var application = new ExternalApplication(applicationMonitor, executablePath, applicationLogger, nativeMethods, processFactory, settings, ONE_SECOND);
return application;
}
@ -82,14 +87,14 @@ namespace SafeExamBrowser.Applications
var paths = new List<string[]>();
var registryPath = QueryPathFromRegistry(settings);
mainExecutable = default(string);
mainExecutable = default;
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), settings.ExecutableName });
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), settings.ExecutableName });
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.System), settings.ExecutableName });
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), settings.ExecutableName });
if (settings.ExecutablePath != default(string))
if (settings.ExecutablePath != default)
{
paths.Add(new[] { settings.ExecutablePath, settings.ExecutableName });
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), settings.ExecutablePath, settings.ExecutableName });
@ -98,11 +103,11 @@ namespace SafeExamBrowser.Applications
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), settings.ExecutablePath, settings.ExecutableName });
}
if (registryPath != default(string))
if (registryPath != default)
{
paths.Add(new[] { registryPath, settings.ExecutableName });
if (settings.ExecutablePath != default(string))
if (settings.ExecutablePath != default)
{
paths.Add(new[] { registryPath, settings.ExecutablePath, settings.ExecutableName });
}
@ -131,22 +136,12 @@ namespace SafeExamBrowser.Applications
private string QueryPathFromRegistry(WhitelistApplication settings)
{
try
if (registry.TryRead($@"{RegistryValue.MachineHive.AppPaths_Key}\{settings.ExecutableName}", "Path", out var value))
{
using (var key = Registry.LocalMachine.OpenSubKey($@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\{settings.ExecutableName}"))
{
if (key != null)
{
return key.GetValue("Path") as string;
}
}
}
catch (Exception e)
{
logger.Error($"Failed to query path in registry for '{settings.ExecutableName}'!", e);
return value as string;
}
return default(string);
return default;
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -11,7 +11,7 @@ using System.Collections.Generic;
using System.Linq;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Settings.Applications;
@ -19,17 +19,18 @@ using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications
{
internal class ExternalApplication : IApplication
internal class ExternalApplication : IApplication<IApplicationWindow>
{
private readonly object @lock = new object();
private IApplicationMonitor applicationMonitor;
private string executablePath;
private IModuleLogger logger;
private INativeMethods nativeMethods;
private IList<ExternalApplicationInstance> instances;
private IProcessFactory processFactory;
private WhitelistApplication settings;
private readonly IApplicationMonitor applicationMonitor;
private readonly string executablePath;
private readonly IList<ExternalApplicationInstance> instances;
private readonly IModuleLogger logger;
private readonly INativeMethods nativeMethods;
private readonly IProcessFactory processFactory;
private readonly WhitelistApplication settings;
private readonly int windowMonitoringInterval;
public bool AutoStart { get; private set; }
public IconResource Icon { get; private set; }
@ -45,7 +46,8 @@ namespace SafeExamBrowser.Applications
IModuleLogger logger,
INativeMethods nativeMethods,
IProcessFactory processFactory,
WhitelistApplication settings)
WhitelistApplication settings,
int windowMonitoringInterval_ms)
{
this.applicationMonitor = applicationMonitor;
this.executablePath = executablePath;
@ -54,6 +56,7 @@ namespace SafeExamBrowser.Applications
this.instances = new List<ExternalApplicationInstance>();
this.processFactory = processFactory;
this.settings = settings;
this.windowMonitoringInterval = windowMonitoringInterval_ms;
}
public IEnumerable<IApplicationWindow> GetWindows()
@ -89,18 +92,6 @@ namespace SafeExamBrowser.Applications
}
}
private string[] BuildArguments()
{
var arguments = new List<string>();
foreach (var argument in settings.Arguments)
{
arguments.Add(Environment.ExpandEnvironmentVariables(argument));
}
return arguments.ToArray();
}
public void Terminate()
{
applicationMonitor.InstanceStarted -= ApplicationMonitor_InstanceStarted;
@ -153,12 +144,24 @@ namespace SafeExamBrowser.Applications
WindowsChanged?.Invoke();
}
private string[] BuildArguments()
{
var arguments = new List<string>();
foreach (var argument in settings.Arguments)
{
arguments.Add(Environment.ExpandEnvironmentVariables(argument));
}
return arguments.ToArray();
}
private void InitializeInstance(IProcess process)
{
lock (@lock)
{
var instanceLogger = logger.CloneFor($"{Name} ({process.Id})");
var instance = new ExternalApplicationInstance(Icon, instanceLogger, nativeMethods, process);
var instance = new ExternalApplicationInstance(Icon, instanceLogger, nativeMethods, process, windowMonitoringInterval);
instance.Terminated += Instance_Terminated;
instance.WindowsChanged += () => WindowsChanged?.Invoke();

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -12,8 +12,8 @@ using System.Linq;
using System.Timers;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
using SafeExamBrowser.Applications.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.WindowsApi.Contracts;
@ -23,24 +23,32 @@ namespace SafeExamBrowser.Applications
{
private readonly object @lock = new object();
private IconResource icon;
private ILogger logger;
private INativeMethods nativeMethods;
private IProcess process;
private readonly IconResource icon;
private readonly ILogger logger;
private readonly INativeMethods nativeMethods;
private readonly IProcess process;
private readonly int windowMonitoringInterval;
private readonly IList<ExternalApplicationWindow> windows;
private Timer timer;
private IList<ExternalApplicationWindow> windows;
internal int Id { get; private set; }
internal event InstanceTerminatedEventHandler Terminated;
internal event WindowsChangedEventHandler WindowsChanged;
internal ExternalApplicationInstance(IconResource icon, ILogger logger, INativeMethods nativeMethods, IProcess process)
internal ExternalApplicationInstance(
IconResource icon,
ILogger logger,
INativeMethods nativeMethods,
IProcess process,
int windowMonitoringInterval_ms)
{
this.icon = icon;
this.logger = logger;
this.nativeMethods = nativeMethods;
this.process = process;
this.windowMonitoringInterval = windowMonitoringInterval_ms;
this.windows = new List<ExternalApplicationWindow>();
}
@ -145,19 +153,20 @@ namespace SafeExamBrowser.Applications
private void InitializeEvents()
{
const int ONE_SECOND = 1000;
process.Terminated += Process_Terminated;
timer = new Timer(ONE_SECOND);
timer = new Timer(windowMonitoringInterval);
timer.Elapsed += Timer_Elapsed;
timer.Start();
}
private void FinalizeEvents()
{
timer.Elapsed -= Timer_Elapsed;
timer.Stop();
if (timer != default)
{
timer.Elapsed -= Timer_Elapsed;
timer.Stop();
}
process.Terminated -= Process_Terminated;
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -9,14 +9,14 @@
using System;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications
{
internal class ExternalApplicationWindow : IApplicationWindow
{
private INativeMethods nativeMethods;
private readonly INativeMethods nativeMethods;
public IntPtr Handle { get; }
public IconResource Icon { get; private set; }

View file

@ -1,4 +1,5 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
@ -8,12 +9,13 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Applications")]
[assembly: AssemblyCopyright("Copyright © 2020 ETH Zürich, Educational Development and Technology (LET)")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
[assembly: InternalsVisibleTo("SafeExamBrowser.Applications.UnitTests")]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("a113e68f-1209-4689-981a-15c554b2df4e")]

View file

@ -9,9 +9,10 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.Applications</RootNamespace>
<AssemblyName>SafeExamBrowser.Applications</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
@ -66,6 +67,10 @@
<Project>{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}</Project>
<Name>SafeExamBrowser.Applications.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Core.Contracts\SafeExamBrowser.Core.Contracts.csproj">
<Project>{fe0e1224-b447-4b14-81e7-ed7d84822aa0}</Project>
<Name>SafeExamBrowser.Core.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Logging.Contracts\SafeExamBrowser.Logging.Contracts.csproj">
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
<Name>SafeExamBrowser.Logging.Contracts</Name>
@ -78,6 +83,10 @@
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
<Name>SafeExamBrowser.Settings</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.SystemComponents.Contracts\SafeExamBrowser.SystemComponents.Contracts.csproj">
<Project>{903129c6-e236-493b-9ad6-c6a57f647a3a}</Project>
<Name>SafeExamBrowser.SystemComponents.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.WindowsApi.Contracts\SafeExamBrowser.WindowsApi.Contracts.csproj">
<Project>{7016f080-9aa5-41b2-a225-385ad877c171}</Project>
<Name>SafeExamBrowser.WindowsApi.Contracts</Name>

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Browser.Contracts.Events
{
/// <summary>
/// Event handler used to indicate that the user wants to move the focus away from the item.
/// </summary>
public delegate void LoseFocusRequestedEventHandler(bool forward);
}

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Browser.Contracts.Events
{
/// <summary>
/// Event handler used to indicate that the user pressed the tab key to move the focus forward or backward.
/// </summary>
public delegate void TabPressedEventHandler(bool forward);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -9,7 +9,7 @@
namespace SafeExamBrowser.Browser.Contracts.Events
{
/// <summary>
/// Event handler used to indicate that the browser has detected a session identifier of a LMS.
/// Event handler used to indicate that the browser has detected a user identifier of an LMS.
/// </summary>
public delegate void SessionIdentifierDetectedEventHandler(string identifier);
public delegate void UserIdentifierDetectedEventHandler(string identifier);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -14,7 +14,7 @@ namespace SafeExamBrowser.Browser.Contracts
/// <summary>
/// Controls the lifetime and functionality of the browser application.
/// </summary>
public interface IBrowserApplication : IApplication
public interface IBrowserApplication : IApplication<IBrowserWindow>
{
/// <summary>
/// Event fired when the browser application detects a download request for an application configuration file.
@ -22,13 +22,24 @@ namespace SafeExamBrowser.Browser.Contracts
event DownloadRequestedEventHandler ConfigurationDownloadRequested;
/// <summary>
/// Event fired when the browser application detects a session identifier of an LMS.
/// Event fired when the user tries to focus the taskbar.
/// </summary>
event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
event LoseFocusRequestedEventHandler LoseFocusRequested;
/// <summary>
/// Event fired when the browser application detects a request to terminate SEB.
/// </summary>
event TerminationRequestedEventHandler TerminationRequested;
/// <summary>
/// Event fired when the browser application detects a user identifier of an LMS.
/// </summary>
event UserIdentifierDetectedEventHandler UserIdentifierDetected;
/// <summary>
/// Transfers the focus to the browser application. If the parameter is <c>true</c>, the first focusable element in the browser window
/// receives focus (passing forward of focus). Otherwise, the last element receives focus.
/// </summary>
void Focus(bool forward);
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Applications.Contracts;
namespace SafeExamBrowser.Browser.Contracts
{
/// <summary>
/// Defines a window of the <see cref="IBrowserApplication"/>.
/// </summary>
public interface IBrowserWindow : IApplicationWindow
{
/// <summary>
/// Indicates whether the window is the main browser window.
/// </summary>
bool IsMainWindow { get; }
/// <summary>
/// The currently loaded URL, or <c>default(string)</c> in case no navigation has happened yet.
/// </summary>
string Url { get; }
}
}

View file

@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Browser.Contracts")]
[assembly: AssemblyCopyright("Copyright © 2020 ETH Zürich, Educational Development and Technology (LET)")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from

View file

@ -9,9 +9,10 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.Browser.Contracts</RootNamespace>
<AssemblyName>SafeExamBrowser.Browser.Contracts</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
@ -57,13 +58,16 @@
<Compile Include="Events\DownloadEventArgs.cs" />
<Compile Include="Events\DownloadFinishedCallback.cs" />
<Compile Include="Events\DownloadRequestedEventHandler.cs" />
<Compile Include="Events\SessionIdentifierDetectedEventHandler.cs" />
<Compile Include="Events\TabPressedEventHandler.cs" />
<Compile Include="Events\LoseFocusRequestedEventHandler.cs" />
<Compile Include="Events\UserIdentifierDetectedEventHandler.cs" />
<Compile Include="Events\TerminationRequestedEventHandler.cs" />
<Compile Include="Filters\IRequestFilter.cs" />
<Compile Include="Filters\IRule.cs" />
<Compile Include="Filters\IRuleFactory.cs" />
<Compile Include="Filters\Request.cs" />
<Compile Include="IBrowserApplication.cs" />
<Compile Include="IBrowserWindow.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla internal
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -31,7 +31,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
[TestMethod]
public void MustCorrectlyCancelDialog()
{
RequestDialog(default(CefFileDialogMode), false);
RequestDialog(default, false);
}
[TestMethod]
@ -71,7 +71,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
var threadId = default(int);
callback.Setup(c => c.Cancel()).Callback(() => sync.Set());
callback.Setup(c => c.Continue(It.IsAny<int>(), It.IsAny<List<string>>())).Callback(() => sync.Set());
callback.Setup(c => c.Continue(It.IsAny<List<string>>())).Callback(() => sync.Set());
sut.DialogRequested += (a) =>
{
args = a;
@ -80,18 +80,18 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
threadId = Thread.CurrentThread.ManagedThreadId;
};
var status = sut.OnFileDialog(default(IWebBrowser), default(IBrowser), mode, default(CefFileDialogFlags), title, initialPath, default(List<string>), default(int), callback.Object);
var status = sut.OnFileDialog(default, default, mode, title, initialPath, default, callback.Object);
sync.WaitOne();
if (confirm)
{
callback.Verify(c => c.Continue(It.IsAny<int>(), It.IsAny<List<string>>()), Times.Once);
callback.Verify(c => c.Continue(It.IsAny<List<string>>()), Times.Once);
callback.Verify(c => c.Cancel(), Times.Never);
}
else
{
callback.Verify(c => c.Continue(It.IsAny<int>(), It.IsAny<List<string>>()), Times.Never);
callback.Verify(c => c.Continue(It.IsAny<List<string>>()), Times.Never);
callback.Verify(c => c.Cancel(), Times.Once);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -7,8 +7,6 @@
*/
using System.Collections.Generic;
using CefSharp;
using CefSharp.Structs;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SafeExamBrowser.Browser.Handlers;
@ -30,9 +28,10 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
var text = default(string);
Assert.IsFalse(sut.OnAutoResize(default(IWebBrowser), default(IBrowser), default(Size)));
Assert.IsFalse(sut.OnConsoleMessage(default(IWebBrowser), default(ConsoleMessageEventArgs)));
Assert.IsFalse(sut.OnTooltipChanged(default(IWebBrowser), ref text));
Assert.IsFalse(sut.OnAutoResize(default, default, default));
Assert.IsFalse(sut.OnConsoleMessage(default, default));
Assert.IsFalse(sut.OnCursorChange(default, default, default, default, default));
Assert.IsFalse(sut.OnTooltipChanged(default, ref text));
}
[TestMethod]
@ -47,12 +46,12 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
called = true;
url = u;
};
sut.OnFaviconUrlChange(default(IWebBrowser), default(IBrowser), new List<string>());
sut.OnFaviconUrlChange(default, default, new List<string>());
Assert.AreEqual(default(string), url);
Assert.AreEqual(default, url);
Assert.IsFalse(called);
sut.OnFaviconUrlChange(default(IWebBrowser), default(IBrowser), new List<string> { newUrl });
sut.OnFaviconUrlChange(default, default, new List<string> { newUrl });
Assert.AreEqual(newUrl, url);
Assert.IsTrue(called);
@ -65,7 +64,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
var actual = default(double);
sut.ProgressChanged += (p) => actual = p;
sut.OnLoadingProgressChange(default(IWebBrowser), default(IBrowser), expected);
sut.OnLoadingProgressChange(default, default, expected);
Assert.AreEqual(expected, actual);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -25,14 +25,52 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
}
[TestMethod]
public void MustDetectReload()
public void MustDetectFindCommand()
{
var findRequested = false;
sut.FindRequested += () => findRequested = true;
var handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.F, default(int), CefEventFlags.ControlDown, default(bool));
Assert.IsTrue(findRequested);
Assert.IsFalse(handled);
findRequested = false;
handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), default(KeyType), default(int), default(int), CefEventFlags.ControlDown, default(bool));
Assert.IsFalse(findRequested);
Assert.IsFalse(handled);
}
[TestMethod]
public void MustDetectHomeNavigationCommand()
{
var homeRequested = false;
sut.HomeNavigationRequested += () => homeRequested = true;
var handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.Home, default(int), default(CefEventFlags), default(bool));
Assert.IsTrue(homeRequested);
Assert.IsFalse(handled);
homeRequested = false;
handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), default(KeyType), default(int), default(int), default(CefEventFlags), default(bool));
Assert.IsFalse(homeRequested);
Assert.IsFalse(handled);
}
[TestMethod]
public void MustDetectReloadCommand()
{
var isShortcut = default(bool);
var reloadRequested = false;
sut.ReloadRequested += () => reloadRequested = true;
var handled = sut.OnPreKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int)Keys.F5, default(int), default(CefEventFlags), default(bool), ref isShortcut);
var handled = sut.OnPreKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.F5, default(int), default(CefEventFlags), default(bool), ref isShortcut);
Assert.IsTrue(reloadRequested);
Assert.IsTrue(handled);

View file

@ -1,49 +0,0 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using CefSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Browser.Handlers;
namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
[TestClass]
public class LifeSpanHandlerTests
{
private LifeSpanHandler sut;
[TestInitialize]
public void Initialize()
{
sut = new LifeSpanHandler();
}
[TestMethod]
public void MustUseDefaultBehavior()
{
Assert.IsFalse(sut.DoClose(default(IWebBrowser), default(IBrowser)));
}
[TestMethod]
public void MustHandlePopup()
{
var args = default(PopupRequestedEventArgs);
var jsAccess = false;
var url = "https://www.host.org/some-url";
sut.PopupRequested += (a) => args = a;
var result = sut.OnBeforePopup(default(IWebBrowser), default(IBrowser), default(IFrame), url, default(string), default(WindowOpenDisposition), default(bool), default(IPopupFeatures), default(IWindowInfo), default(IBrowserSettings), ref jsAccess, out var newBrowser);
Assert.IsTrue(result);
Assert.AreEqual(default(IWebBrowser), newBrowser);
Assert.AreEqual(url, args.Url);
}
}
}

View file

@ -1,17 +1,19 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using CefSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser;
@ -28,6 +30,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
private AppConfig appConfig;
private Mock<IRequestFilter> filter;
private Mock<IKeyGenerator> keyGenerator;
private Mock<ILogger> logger;
private BrowserSettings settings;
private WindowSettings windowSettings;
@ -40,13 +43,23 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
appConfig = new AppConfig();
filter = new Mock<IRequestFilter>();
keyGenerator = new Mock<IKeyGenerator>();
logger = new Mock<ILogger>();
settings = new BrowserSettings();
windowSettings = new WindowSettings();
text = new Mock<IText>();
resourceHandler = new ResourceHandler(appConfig, filter.Object, logger.Object, settings, windowSettings, text.Object);
resourceHandler = new ResourceHandler(appConfig, filter.Object, keyGenerator.Object, logger.Object, default, settings, windowSettings, text.Object);
sut = new TestableRequestHandler(appConfig, filter.Object, logger.Object, resourceHandler, settings, windowSettings, text.Object);
sut = new TestableRequestHandler(appConfig, filter.Object, logger.Object, resourceHandler, settings, windowSettings);
}
[TestMethod]
public void MustBlockSpecialWindowDispositions()
{
Assert.IsTrue(sut.OnOpenUrlFromTab(default, default, default, default, WindowOpenDisposition.NewBackgroundTab, default));
Assert.IsTrue(sut.OnOpenUrlFromTab(default, default, default, default, WindowOpenDisposition.NewPopup, default));
Assert.IsTrue(sut.OnOpenUrlFromTab(default, default, default, default, WindowOpenDisposition.NewWindow, default));
Assert.IsTrue(sut.OnOpenUrlFromTab(default, default, default, default, WindowOpenDisposition.SaveToDisk, default));
}
[TestMethod]
@ -56,6 +69,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
var quitUrl = "http://www.byebye.com";
var request = new Mock<IRequest>();
appConfig.ConfigurationFileMimeType = "application/seb";
request.SetupGet(r => r.Url).Returns(quitUrl);
settings.QuitUrl = quitUrl;
sut.QuitUrlVisited += (url) => eventFired = true;
@ -107,8 +121,9 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
var eventFired = false;
var request = new Mock<IRequest>();
var url = "www.test.org";
var url = "https://www.test.org";
appConfig.ConfigurationFileMimeType = "application/seb";
filter.Setup(f => f.Process(It.Is<Request>(r => r.Url.Equals(url)))).Returns(FilterResult.Block);
request.SetupGet(r => r.ResourceType).Returns(ResourceType.MainFrame);
request.SetupGet(r => r.Url).Returns(url);
@ -140,8 +155,9 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
var eventFired = false;
var request = new Mock<IRequest>();
var url = "www.test.org";
var url = "https://www.test.org";
appConfig.ConfigurationFileMimeType = "application/seb";
filter.Setup(f => f.Process(It.Is<Request>(r => r.Url.Equals(url)))).Returns(FilterResult.Block);
request.SetupGet(r => r.ResourceType).Returns(ResourceType.SubFrame);
request.SetupGet(r => r.Url).Returns(url);
@ -168,6 +184,75 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
Assert.IsFalse(eventFired);
}
[TestMethod]
public void MustInitiateDataUriConfigurationFileDownload()
{
var browser = new Mock<IBrowser>();
var host = new Mock<IBrowserHost>();
var request = new Mock<IRequest>();
appConfig.ConfigurationFileExtension = ".xyz";
appConfig.ConfigurationFileMimeType = "application/seb";
appConfig.SebUriScheme = "abc";
appConfig.SebUriSchemeSecure = "abcd";
browser.Setup(b => b.GetHost()).Returns(host.Object);
request.SetupGet(r => r.Url).Returns($"{appConfig.SebUriSchemeSecure}://{appConfig.ConfigurationFileMimeType};base64,H4sIAAAAAAAAE41WbXPaRhD...");
var handled = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), browser.Object, Mock.Of<IFrame>(), request.Object, false, false);
host.Verify(h => h.StartDownload(It.Is<string>(u => u == $"data:{appConfig.ConfigurationFileMimeType};base64,H4sIAAAAAAAAE41WbXPaRhD...")));
Assert.IsTrue(handled);
}
[TestMethod]
public void MustInitiateHttpConfigurationFileDownload()
{
var browser = new Mock<IBrowser>();
var host = new Mock<IBrowserHost>();
var request = new Mock<IRequest>();
appConfig.ConfigurationFileExtension = ".xyz";
appConfig.ConfigurationFileMimeType = "application/seb";
appConfig.SebUriScheme = "abc";
appConfig.SebUriSchemeSecure = "abcd";
browser.Setup(b => b.GetHost()).Returns(host.Object);
request.SetupGet(r => r.Url).Returns($"{appConfig.SebUriScheme}://host.com/path/file{appConfig.ConfigurationFileExtension}");
var handled = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), browser.Object, Mock.Of<IFrame>(), request.Object, false, false);
host.Verify(h => h.StartDownload(It.Is<string>(u => u == $"{Uri.UriSchemeHttp}://host.com/path/file{appConfig.ConfigurationFileExtension}")));
Assert.IsTrue(handled);
}
[TestMethod]
public void MustInitiateHttpsConfigurationFileDownload()
{
var browser = new Mock<IBrowser>();
var host = new Mock<IBrowserHost>();
var request = new Mock<IRequest>();
appConfig.ConfigurationFileExtension = ".xyz";
appConfig.ConfigurationFileMimeType = "application/seb";
appConfig.SebUriScheme = "abc";
appConfig.SebUriSchemeSecure = "abcd";
browser.Setup(b => b.GetHost()).Returns(host.Object);
request.SetupGet(r => r.Url).Returns($"{appConfig.SebUriSchemeSecure}://host.com/path/file{appConfig.ConfigurationFileExtension}");
var handled = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), browser.Object, Mock.Of<IFrame>(), request.Object, false, false);
host.Verify(h => h.StartDownload(It.Is<string>(u => u == $"{Uri.UriSchemeHttps}://host.com/path/file{appConfig.ConfigurationFileExtension}")));
Assert.IsTrue(handled);
}
[TestMethod]
public void MustReturnResourceHandler()
{
var disableDefaultHandling = default(bool);
var handler = sut.GetResourceRequestHandler(default, default, default, default, default, default, default, ref disableDefaultHandling);
Assert.AreSame(resourceHandler, handler);
}
[TestMethod]
public void MustUseProxyCredentials()
{
@ -178,7 +263,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
settings.Proxy.Proxies.Add(proxy1);
settings.Proxy.Proxies.Add(proxy2);
var result = sut.GetAuthCredentials(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), default(string), true, "WWW.tEst.Com", 10, default(string), default(string), callback.Object);
var result = sut.GetAuthCredentials(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), default, true, "WWW.tEst.Com", 10, default, default, callback.Object);
callback.Verify(c => c.Cancel(), Times.Never);
callback.Verify(c => c.Continue(It.Is<string>(u => u.Equals(proxy1.Username)), It.Is<string>(p => p.Equals(proxy1.Password))), Times.Once);
@ -192,7 +277,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
var callback = new Mock<IAuthCallback>();
sut.GetAuthCredentials(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), default(string), false, default(string), default(int), default(string), default(string), callback.Object);
sut.GetAuthCredentials(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), default, false, default, default, default, default, callback.Object);
callback.Verify(c => c.Cancel(), Times.Never);
callback.Verify(c => c.Continue(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
@ -200,14 +285,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
private class TestableRequestHandler : RequestHandler
{
internal TestableRequestHandler(
AppConfig appConfig,
IRequestFilter filter,
ILogger logger,
ResourceHandler resourceHandler,
BrowserSettings settings,
WindowSettings windowSettings,
IText text) : base(appConfig, filter, logger, resourceHandler, settings, windowSettings, text)
internal TestableRequestHandler(AppConfig appConfig, IRequestFilter filter, ILogger logger, ResourceHandler resourceHandler, BrowserSettings settings, WindowSettings windowSettings) : base(appConfig, filter, logger, resourceHandler, settings, windowSettings)
{
}
@ -216,10 +294,20 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
return base.GetAuthCredentials(webBrowser, browser, originUrl, isProxy, host, port, realm, scheme, callback);
}
public new IResourceRequestHandler GetResourceRequestHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
{
return base.GetResourceRequestHandler(webBrowser, browser, frame, request, isNavigation, isDownload, requestInitiator, ref disableDefaultHandling);
}
public new bool OnBeforeBrowse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect)
{
return base.OnBeforeBrowse(webBrowser, browser, frame, request, userGesture, isRedirect);
}
public new bool OnOpenUrlFromTab(IWebBrowser webBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture)
{
return base.OnOpenUrlFromTab(webBrowser, browser, frame, targetUrl, targetDisposition, userGesture);
}
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -9,13 +9,16 @@
using System;
using System.Collections.Specialized;
using System.Net.Mime;
using System.Threading;
using CefSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings;
using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.Settings.Browser.Filter;
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
@ -29,6 +32,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
private AppConfig appConfig;
private Mock<IRequestFilter> filter;
private Mock<IKeyGenerator> keyGenerator;
private Mock<ILogger> logger;
private BrowserSettings settings;
private WindowSettings windowSettings;
@ -40,12 +44,13 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
appConfig = new AppConfig();
filter = new Mock<IRequestFilter>();
keyGenerator = new Mock<IKeyGenerator>();
logger = new Mock<ILogger>();
settings = new BrowserSettings();
windowSettings = new WindowSettings();
text = new Mock<IText>();
sut = new TestableResourceHandler(appConfig, filter.Object, logger.Object, settings, windowSettings, text.Object);
sut = new TestableResourceHandler(appConfig, filter.Object, keyGenerator.Object, logger.Object, SessionMode.Server, settings, windowSettings, text.Object);
}
[TestMethod]
@ -56,11 +61,13 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
var request = new Mock<IRequest>();
browser.SetupGet(b => b.Address).Returns("http://www.host.org");
keyGenerator.Setup(g => g.CalculateBrowserExamKeyHash(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<string>())).Returns(new Random().Next().ToString());
keyGenerator.Setup(g => g.CalculateConfigurationKeyHash(It.IsAny<string>(), It.IsAny<string>())).Returns(new Random().Next().ToString());
request.SetupGet(r => r.Headers).Returns(new NameValueCollection());
request.SetupGet(r => r.Url).Returns("http://www.host.org");
request.SetupSet(r => r.Headers = It.IsAny<NameValueCollection>()).Callback<NameValueCollection>((h) => headers = h);
settings.SendConfigurationKey = true;
settings.SendExamKey = true;
settings.SendBrowserExamKey = true;
var result = sut.OnBeforeResourceLoad(browser.Object, Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, Mock.Of<IRequestCallback>());
@ -73,18 +80,46 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
}
[TestMethod]
public void MustNotAppendCustomHeadersForCrossDomain()
public void MustAppendCustomHeadersForCrossDomainResourceRequestAndMainFrame()
{
var browser = new Mock<IWebBrowser>();
var headers = new NameValueCollection();
var request = new Mock<IRequest>();
browser.SetupGet(b => b.Address).Returns("http://www.otherhost.org");
keyGenerator.Setup(g => g.CalculateBrowserExamKeyHash(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<string>())).Returns(new Random().Next().ToString());
keyGenerator.Setup(g => g.CalculateConfigurationKeyHash(It.IsAny<string>(), It.IsAny<string>())).Returns(new Random().Next().ToString());
request.SetupGet(r => r.ResourceType).Returns(ResourceType.MainFrame);
request.SetupGet(r => r.Headers).Returns(new NameValueCollection());
request.SetupGet(r => r.Url).Returns("http://www.host.org");
request.SetupSet(r => r.Headers = It.IsAny<NameValueCollection>()).Callback<NameValueCollection>((h) => headers = h);
settings.SendConfigurationKey = true;
settings.SendExamKey = true;
settings.SendBrowserExamKey = true;
var result = sut.OnBeforeResourceLoad(browser.Object, Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, Mock.Of<IRequestCallback>());
request.VerifyGet(r => r.Headers, Times.AtLeastOnce);
request.VerifySet(r => r.Headers = It.IsAny<NameValueCollection>(), Times.AtLeastOnce);
Assert.AreEqual(CefReturnValue.Continue, result);
Assert.IsNotNull(headers["X-SafeExamBrowser-ConfigKeyHash"]);
Assert.IsNotNull(headers["X-SafeExamBrowser-RequestHash"]);
}
[TestMethod]
public void MustNotAppendCustomHeadersForCrossDomainResourceRequestAndSubResource()
{
var browser = new Mock<IWebBrowser>();
var headers = new NameValueCollection();
var request = new Mock<IRequest>();
browser.SetupGet(b => b.Address).Returns("http://www.otherhost.org");
request.SetupGet(r => r.ResourceType).Returns(ResourceType.SubResource);
request.SetupGet(r => r.Headers).Returns(new NameValueCollection());
request.SetupGet(r => r.Url).Returns("http://www.host.org");
request.SetupSet(r => r.Headers = It.IsAny<NameValueCollection>()).Callback<NameValueCollection>((h) => headers = h);
settings.SendConfigurationKey = true;
settings.SendBrowserExamKey = true;
var result = sut.OnBeforeResourceLoad(browser.Object, Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, Mock.Of<IRequestCallback>());
@ -113,7 +148,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
public void MustFilterContentRequests()
{
var request = new Mock<IRequest>();
var url = "www.test.org";
var url = "http://www.test.org";
filter.Setup(f => f.Process(It.Is<Request>(r => r.Url.Equals(url)))).Returns(FilterResult.Block);
request.SetupGet(r => r.ResourceType).Returns(ResourceType.SubFrame);
@ -128,10 +163,16 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
Assert.IsNotNull(resourceHandler);
}
[TestMethod]
public void MustLetOperatingSystemHandleUnknownProtocols()
{
Assert.IsTrue(sut.OnProtocolExecution(default, default, default, default));
}
[TestMethod]
public void MustRedirectToDisablePdfToolbar()
{
var browser = new Mock<IWebBrowser>();
var frame = new Mock<IFrame>();
var headers = new NameValueCollection { { "Content-Type", MediaTypeNames.Application.Pdf } };
var request = new Mock<IRequest>();
var response = new Mock<IResponse>();
@ -143,17 +184,17 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
settings.AllowPdfReader = true;
settings.AllowPdfReaderToolbar = false;
var result = sut.OnResourceResponse(browser.Object, Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
var result = sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), frame.Object, request.Object, response.Object);
browser.Verify(b => b.Load(It.Is<string>(s => s.Equals($"{url}#toolbar=0"))), Times.Once);
frame.Verify(b => b.LoadUrl(It.Is<string>(s => s.Equals($"{url}#toolbar=0"))), Times.Once);
Assert.IsTrue(result);
browser.Reset();
frame.Reset();
request.SetupGet(r => r.Url).Returns($"{url}#toolbar=0");
result = sut.OnResourceResponse(browser.Object, Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
result = sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), frame.Object, request.Object, response.Object);
browser.Verify(b => b.Load(It.IsAny<string>()), Times.Never);
frame.Verify(b => b.LoadUrl(It.IsAny<string>()), Times.Never);
Assert.IsFalse(result);
}
@ -183,15 +224,113 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
Assert.AreEqual("https://www.host.org/", url);
}
[TestMethod]
public void MustSearchGenericLmsSessionIdentifier()
{
var @event = new AutoResetEvent(false);
var headers = new NameValueCollection();
var newUrl = default(string);
var request = new Mock<IRequest>();
var response = new Mock<IResponse>();
var userId = default(string);
headers.Add("X-LMS-USER-ID", "some-session-id-123");
request.SetupGet(r => r.Url).Returns("https://www.somelms.org");
response.SetupGet(r => r.Headers).Returns(headers);
sut.UserIdentifierDetected += (id) =>
{
userId = id;
@event.Set();
};
sut.OnResourceRedirect(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), Mock.Of<IRequest>(), response.Object, ref newUrl);
@event.WaitOne();
Assert.AreEqual("some-session-id-123", userId);
headers.Clear();
headers.Add("X-LMS-USER-ID", "other-session-id-123");
userId = default;
sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
@event.WaitOne();
Assert.AreEqual("other-session-id-123", userId);
}
[TestMethod]
public void MustSearchEdxSessionIdentifier()
{
var @event = new AutoResetEvent(false);
var headers = new NameValueCollection();
var newUrl = default(string);
var request = new Mock<IRequest>();
var response = new Mock<IResponse>();
var userId = default(string);
headers.Add("Set-Cookie", "edx-user-info=\"{\\\"username\\\": \\\"edx-123\\\"}\"; expires");
request.SetupGet(r => r.Url).Returns("https://www.somelms.org");
response.SetupGet(r => r.Headers).Returns(headers);
sut.UserIdentifierDetected += (id) =>
{
userId = id;
@event.Set();
};
sut.OnResourceRedirect(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), Mock.Of<IRequest>(), response.Object, ref newUrl);
@event.WaitOne();
Assert.AreEqual("edx-123", userId);
headers.Clear();
headers.Add("Set-Cookie", "edx-user-info=\"{\\\"username\\\": \\\"edx-345\\\"}\"; expires");
userId = default;
sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
@event.WaitOne();
Assert.AreEqual("edx-345", userId);
}
[TestMethod]
public void MustSearchMoodleSessionIdentifier()
{
var @event = new AutoResetEvent(false);
var headers = new NameValueCollection();
var newUrl = default(string);
var request = new Mock<IRequest>();
var response = new Mock<IResponse>();
var userId = default(string);
headers.Add("Location", "https://www.some-moodle-instance.org/moodle/login/index.php?testsession=123");
request.SetupGet(r => r.Url).Returns("https://www.some-moodle-instance.org");
response.SetupGet(r => r.Headers).Returns(headers);
sut.UserIdentifierDetected += (id) =>
{
userId = id;
@event.Set();
};
sut.OnResourceRedirect(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), Mock.Of<IRequest>(), response.Object, ref newUrl);
@event.WaitOne();
Assert.AreEqual("123", userId);
headers.Clear();
headers.Add("Location", "https://www.some-moodle-instance.org/moodle/login/index.php?testsession=456");
userId = default;
sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
@event.WaitOne();
Assert.AreEqual("456", userId);
}
private class TestableResourceHandler : ResourceHandler
{
internal TestableResourceHandler(
AppConfig appConfig,
IRequestFilter filter,
IKeyGenerator keyGenerator,
ILogger logger,
SessionMode sessionMode,
BrowserSettings settings,
WindowSettings windowSettings,
IText text) : base(appConfig, filter, logger, settings, windowSettings, text)
IText text) : base(appConfig, filter, keyGenerator, logger, sessionMode, settings, windowSettings, text)
{
}
@ -205,6 +344,16 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
return base.OnBeforeResourceLoad(webBrowser, browser, frame, request, callback);
}
public new bool OnProtocolExecution(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request)
{
return base.OnProtocolExecution(webBrowser, browser, frame, request);
}
public new void OnResourceRedirect(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, ref string newUrl)
{
base.OnResourceRedirect(webBrowser, browser, frame, request, response, ref newUrl);
}
public new bool OnResourceResponse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response)
{
return base.OnResourceResponse(webBrowser, browser, frame, request, response);

View file

@ -5,7 +5,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Browser.UnitTests")]
[assembly: AssemblyCopyright("Copyright © 2020 ETH Zürich, Educational Development and Technology (LET)")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
[assembly: ComVisible(false)]

View file

@ -1,9 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.props" Condition="Exists('..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.props')" />
<Import Project="..\packages\cef.redist.x86.86.0.24\build\cef.redist.x86.props" Condition="Exists('..\packages\cef.redist.x86.86.0.24\build\cef.redist.x86.props')" />
<Import Project="..\packages\cef.redist.x64.86.0.24\build\cef.redist.x64.props" Condition="Exists('..\packages\cef.redist.x64.86.0.24\build\cef.redist.x64.props')" />
<Import Project="..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.props')" />
<Import Project="..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props')" />
<Import Project="..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props" Condition="Exists('..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props')" />
<Import Project="..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props" Condition="Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props')" />
<Import Project="..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.props" Condition="Exists('..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.props')" />
<Import Project="..\packages\chromiumembeddedframework.runtime.win-x86.121.3.13\build\chromiumembeddedframework.runtime.win-x86.props" Condition="Exists('..\packages\chromiumembeddedframework.runtime.win-x86.121.3.13\build\chromiumembeddedframework.runtime.win-x86.props')" />
<Import Project="..\packages\chromiumembeddedframework.runtime.win-x64.121.3.13\build\chromiumembeddedframework.runtime.win-x64.props" Condition="Exists('..\packages\chromiumembeddedframework.runtime.win-x64.121.3.13\build\chromiumembeddedframework.runtime.win-x64.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@ -13,7 +15,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.Browser.UnitTests</RootNamespace>
<AssemblyName>SafeExamBrowser.Browser.UnitTests</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
@ -23,6 +25,7 @@
<TestProjectType>UnitTest</TestProjectType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
@ -61,28 +64,88 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\packages\Castle.Core.4.4.1\lib\net45\Castle.Core.dll</HintPath>
<Reference Include="Castle.Core, Version=5.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\packages\Castle.Core.5.1.1\lib\net462\Castle.Core.dll</HintPath>
</Reference>
<Reference Include="CefSharp, Version=121.3.130.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL">
<HintPath>..\packages\CefSharp.Common.121.3.130\lib\net462\CefSharp.dll</HintPath>
</Reference>
<Reference Include="CefSharp.Core, Version=121.3.130.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL">
<HintPath>..\packages\CefSharp.Common.121.3.130\lib\net462\CefSharp.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ApplicationInsights, Version=2.22.0.997, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.2.22.0\lib\net46\Microsoft.ApplicationInsights.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Testing.Extensions.Telemetry, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Extensions.TrxReport.Abstractions, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.TrxReport.Abstractions.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.TrxReport.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Extensions.VSTestBridge, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.VSTestBridge.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.VSTestBridge.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Platform, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\lib\netstandard2.0\Microsoft.Testing.Platform.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Platform.MSBuild, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\lib\netstandard2.0\Microsoft.Testing.Platform.MSBuild.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TestPlatform.CoreUtilities, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.TestPlatform.CoreUtilities.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TestPlatform.PlatformAbstractions, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.TestPlatform.PlatformAbstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.ObjectModel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
<HintPath>..\packages\MSTest.TestFramework.3.2.2\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
<HintPath>..\packages\MSTest.TestFramework.3.2.2\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
</Reference>
<Reference Include="Moq, Version=4.15.0.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.15.2\lib\net45\Moq.dll</HintPath>
<Reference Include="Moq, Version=4.20.70.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.20.70\lib\net462\Moq.dll</HintPath>
</Reference>
<Reference Include="NuGet.Frameworks, Version=6.9.1.3, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\NuGet.Frameworks.6.9.1\lib\net472\NuGet.Frameworks.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Immutable, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.8.0.0\lib\net462\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
<Reference Include="System.Diagnostics.DiagnosticSource, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Diagnostics.DiagnosticSource.8.0.0\lib\net462\System.Diagnostics.DiagnosticSource.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http" />
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Metadata, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reflection.Metadata.8.0.0\lib\net462\System.Reflection.Metadata.dll</HintPath>
</Reference>
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Filters\LegacyFilter.cs" />
@ -95,7 +158,6 @@
<Compile Include="Handlers\DisplayHandlerTests.cs" />
<Compile Include="Handlers\DownloadHandlerTests.cs" />
<Compile Include="Handlers\KeyboardHandlerTests.cs" />
<Compile Include="Handlers\LifeSpanHandlerTests.cs" />
<Compile Include="Handlers\RequestHandlerTests.cs" />
<Compile Include="Handlers\ResourceHandlerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
@ -142,13 +204,17 @@
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.targets'))" />
<Error Condition="!Exists('..\packages\cef.redist.x64.86.0.24\build\cef.redist.x64.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\cef.redist.x64.86.0.24\build\cef.redist.x64.props'))" />
<Error Condition="!Exists('..\packages\cef.redist.x86.86.0.24\build\cef.redist.x86.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\cef.redist.x86.86.0.24\build\cef.redist.x86.props'))" />
<Error Condition="!Exists('..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.props'))" />
<Error Condition="!Exists('..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.targets'))" />
<Error Condition="!Exists('..\packages\chromiumembeddedframework.runtime.win-x64.121.3.13\build\chromiumembeddedframework.runtime.win-x64.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\chromiumembeddedframework.runtime.win-x64.121.3.13\build\chromiumembeddedframework.runtime.win-x64.props'))" />
<Error Condition="!Exists('..\packages\chromiumembeddedframework.runtime.win-x86.121.3.13\build\chromiumembeddedframework.runtime.win-x86.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\chromiumembeddedframework.runtime.win-x86.121.3.13\build\chromiumembeddedframework.runtime.win-x86.props'))" />
<Error Condition="!Exists('..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.props'))" />
<Error Condition="!Exists('..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets'))" />
</Target>
<Import Project="..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.targets')" />
<Import Project="..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.targets" Condition="Exists('..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.targets')" />
<Import Project="..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.targets" Condition="Exists('..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.targets')" />
<Import Project="..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets" Condition="Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets')" />
<Import Project="..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets')" />
</Project>

View file

@ -8,12 +8,44 @@
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Security.Principal.Windows" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="CefSharp" publicKeyToken="40c4b6fc221f4138" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-118.6.80.0" newVersion="118.6.80.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="CefSharp.Core" publicKeyToken="40c4b6fc221f4138" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-118.6.80.0" newVersion="118.6.80.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="NuGet.Frameworks" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.11.3.1" newVersion="5.11.3.1" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.ApplicationInsights" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.22.0.997" newVersion="2.22.0.997" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Metadata" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" /></startup></configuration>

View file

@ -1,12 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Castle.Core" version="4.4.1" targetFramework="net472" />
<package id="cef.redist.x64" version="86.0.24" targetFramework="net472" />
<package id="cef.redist.x86" version="86.0.24" targetFramework="net472" />
<package id="CefSharp.Common" version="86.0.241" targetFramework="net472" />
<package id="Moq" version="4.15.2" targetFramework="net472" />
<package id="MSTest.TestAdapter" version="2.1.2" targetFramework="net472" />
<package id="MSTest.TestFramework" version="2.1.2" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="5.0.0" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
<package id="Castle.Core" version="5.1.1" targetFramework="net48" />
<package id="CefSharp.Common" version="121.3.130" targetFramework="net48" />
<package id="chromiumembeddedframework.runtime.win-x64" version="121.3.13" targetFramework="net48" />
<package id="chromiumembeddedframework.runtime.win-x86" version="121.3.13" targetFramework="net48" />
<package id="Microsoft.ApplicationInsights" version="2.22.0" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.Telemetry" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.TrxReport.Abstractions" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.VSTestBridge" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Platform" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Platform.MSBuild" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.TestPlatform.ObjectModel" version="17.9.0" targetFramework="net48" />
<package id="Moq" version="4.20.70" targetFramework="net48" />
<package id="MSTest.TestAdapter" version="3.2.2" targetFramework="net48" />
<package id="MSTest.TestFramework" version="3.2.2" targetFramework="net48" />
<package id="NuGet.Frameworks" version="6.9.1" targetFramework="net48" />
<package id="System.Buffers" version="4.5.1" targetFramework="net48" />
<package id="System.Collections.Immutable" version="8.0.0" targetFramework="net48" />
<package id="System.Diagnostics.DiagnosticSource" version="8.0.0" targetFramework="net48" />
<package id="System.Memory" version="4.5.5" targetFramework="net48" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net48" />
<package id="System.Reflection.Metadata" version="8.0.0" targetFramework="net48" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net48" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net48" />
</packages>

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -8,21 +8,22 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using CefSharp;
using CefSharp.WinForms;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
using SafeExamBrowser.Browser.Contracts;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.Settings;
using SafeExamBrowser.Settings.Browser.Proxy;
using SafeExamBrowser.Settings.Logging;
using SafeExamBrowser.UserInterface.Contracts;
@ -35,18 +36,21 @@ namespace SafeExamBrowser.Browser
{
public class BrowserApplication : IBrowserApplication
{
private int instanceIdCounter = default(int);
private int windowIdCounter = default;
private AppConfig appConfig;
private List<BrowserApplicationInstance> instances;
private IFileSystemDialog fileSystemDialog;
private IHashAlgorithm hashAlgorithm;
private INativeMethods nativeMethods;
private IMessageBox messageBox;
private IModuleLogger logger;
private BrowserSettings settings;
private IText text;
private IUserInterfaceFactory uiFactory;
private readonly AppConfig appConfig;
private readonly Clipboard clipboard;
private readonly IFileSystemDialog fileSystemDialog;
private readonly IHashAlgorithm hashAlgorithm;
private readonly IKeyGenerator keyGenerator;
private readonly IModuleLogger logger;
private readonly IMessageBox messageBox;
private readonly INativeMethods nativeMethods;
private readonly SessionMode sessionMode;
private readonly BrowserSettings settings;
private readonly IText text;
private readonly IUserInterfaceFactory uiFactory;
private readonly List<BrowserWindow> windows;
public bool AutoStart { get; private set; }
public IconResource Icon { get; private set; }
@ -55,8 +59,9 @@ namespace SafeExamBrowser.Browser
public string Tooltip { get; private set; }
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
public event LoseFocusRequestedEventHandler LoseFocusRequested;
public event TerminationRequestedEventHandler TerminationRequested;
public event UserIdentifierDetectedEventHandler UserIdentifierDetected;
public event WindowsChangedEventHandler WindowsChanged;
public BrowserApplication(
@ -64,27 +69,40 @@ namespace SafeExamBrowser.Browser
BrowserSettings settings,
IFileSystemDialog fileSystemDialog,
IHashAlgorithm hashAlgorithm,
INativeMethods nativeMethods,
IKeyGenerator keyGenerator,
IMessageBox messageBox,
IModuleLogger logger,
INativeMethods nativeMethods,
SessionMode sessionMode,
IText text,
IUserInterfaceFactory uiFactory)
{
this.appConfig = appConfig;
this.clipboard = new Clipboard(logger.CloneFor(nameof(Clipboard)), settings);
this.fileSystemDialog = fileSystemDialog;
this.hashAlgorithm = hashAlgorithm;
this.nativeMethods = nativeMethods;
this.instances = new List<BrowserApplicationInstance>();
this.keyGenerator = keyGenerator;
this.logger = logger;
this.messageBox = messageBox;
this.nativeMethods = nativeMethods;
this.sessionMode = sessionMode;
this.settings = settings;
this.text = text;
this.uiFactory = uiFactory;
this.windows = new List<BrowserWindow>();
}
public IEnumerable<IApplicationWindow> GetWindows()
public void Focus(bool forward)
{
return new List<IApplicationWindow>(instances);
windows.ForEach(window =>
{
window.Focus(forward);
});
}
public IEnumerable<IBrowserWindow> GetWindows()
{
return new List<IBrowserWindow>(windows);
}
public void Initialize()
@ -98,11 +116,18 @@ namespace SafeExamBrowser.Browser
if (success)
{
InitializeIntegrityKeys();
if (settings.DeleteCookiesOnStartup)
{
DeleteCookies();
}
if (settings.UseTemporaryDownAndUploadDirectory)
{
CreateTemporaryDownAndUploadDirectory();
}
logger.Info("Initialized browser.");
}
else
@ -113,18 +138,24 @@ namespace SafeExamBrowser.Browser
public void Start()
{
CreateNewInstance();
CreateNewWindow();
}
public void Terminate()
{
logger.Info("Initiating termination...");
AwaitReady();
foreach (var instance in instances)
foreach (var window in windows)
{
instance.Terminated -= Instance_Terminated;
instance.Terminate();
logger.Info($"Terminated browser instance {instance.Id}.");
window.Closed -= Window_Closed;
window.Close();
logger.Info($"Closed browser window #{window.Id}.");
}
if (settings.UseTemporaryDownAndUploadDirectory)
{
DeleteTemporaryDownAndUploadDirectory();
}
if (settings.DeleteCookiesOnShutdown)
@ -145,28 +176,88 @@ namespace SafeExamBrowser.Browser
}
}
private void CreateNewInstance(string url = null)
private void AwaitReady()
{
var id = ++instanceIdCounter;
var isMainInstance = instances.Count == 0;
var instanceLogger = logger.CloneFor($"Browser Instance #{id}");
var startUrl = url ?? GenerateStartUrl();
var instance = new BrowserApplicationInstance(appConfig, settings, id, isMainInstance, fileSystemDialog, hashAlgorithm, messageBox, instanceLogger, text, uiFactory, startUrl);
// We apparently need to let the browser finish any pending work before attempting to reset or terminate it, especially if the
// reset or termination is initiated automatically (e.g. by a quit URL). Otherwise, the engine will crash on some occasions, seemingly
// when it can't finish handling its events (like ChromiumWebBrowser.LoadError).
instance.ConfigurationDownloadRequested += (fileName, args) => ConfigurationDownloadRequested?.Invoke(fileName, args);
instance.PopupRequested += Instance_PopupRequested;
instance.ResetRequested += Instance_ResetRequested;
instance.SessionIdentifierDetected += (i) => SessionIdentifierDetected?.Invoke(i);
instance.Terminated += Instance_Terminated;
instance.TerminationRequested += () => TerminationRequested?.Invoke();
Thread.Sleep(500);
}
instance.Initialize();
instances.Add(instance);
private void CreateNewWindow(PopupRequestedEventArgs args = default)
{
var id = ++windowIdCounter;
var isMainWindow = windows.Count == 0;
var startUrl = GenerateStartUrl();
var windowLogger = logger.CloneFor($"Browser Window #{id}");
var window = new BrowserWindow(
appConfig,
clipboard,
fileSystemDialog,
hashAlgorithm,
id,
isMainWindow,
keyGenerator,
windowLogger,
messageBox,
sessionMode,
settings,
startUrl,
text,
uiFactory);
logger.Info($"Created browser instance {instance.Id}.");
window.Closed += Window_Closed;
window.ConfigurationDownloadRequested += (f, a) => ConfigurationDownloadRequested?.Invoke(f, a);
window.PopupRequested += Window_PopupRequested;
window.ResetRequested += Window_ResetRequested;
window.UserIdentifierDetected += (i) => UserIdentifierDetected?.Invoke(i);
window.TerminationRequested += () => TerminationRequested?.Invoke();
window.LoseFocusRequested += (forward) => LoseFocusRequested?.Invoke(forward);
window.InitializeControl();
windows.Add(window);
if (args != default(PopupRequestedEventArgs))
{
args.Window = window;
}
else
{
window.InitializeWindow();
}
logger.Info($"Created browser window #{window.Id}.");
WindowsChanged?.Invoke();
}
private void CreateTemporaryDownAndUploadDirectory()
{
try
{
settings.DownAndUploadDirectory = Path.Combine(appConfig.TemporaryDirectory, Path.GetRandomFileName());
Directory.CreateDirectory(settings.DownAndUploadDirectory);
logger.Info($"Created temporary down- and upload directory.");
}
catch (Exception e)
{
logger.Error("Failed to create temporary down- and upload directory!", e);
}
}
private void DeleteTemporaryDownAndUploadDirectory()
{
try
{
Directory.Delete(settings.DownAndUploadDirectory, true);
logger.Info("Deleted temporary down- and upload directory.");
}
catch (Exception e)
{
logger.Error("Failed to delete temporary down- and upload directory!", e);
}
}
private void DeleteCache()
{
try
@ -240,6 +331,7 @@ namespace SafeExamBrowser.Browser
var error = logger.LogLevel == LogLevel.Error;
var cefSettings = new CefSettings();
cefSettings.AcceptLanguageList = CultureInfo.CurrentUICulture.Name;
cefSettings.CachePath = appConfig.BrowserCachePath;
cefSettings.CefCommandLineArgs.Add("touch-events", "enabled");
cefSettings.LogFile = appConfig.BrowserLogFilePath;
@ -247,6 +339,11 @@ namespace SafeExamBrowser.Browser
cefSettings.PersistSessionCookies = !settings.DeleteCookiesOnStartup || !settings.DeleteCookiesOnShutdown;
cefSettings.UserAgent = InitializeUserAgent();
if (!settings.AllowPageZoom)
{
cefSettings.CefCommandLineArgs.Add("disable-pinch");
}
if (!settings.AllowPdfReader)
{
cefSettings.CefCommandLineArgs.Add("disable-pdf-extension");
@ -257,8 +354,13 @@ namespace SafeExamBrowser.Browser
cefSettings.CefCommandLineArgs.Add("disable-spell-checking");
}
cefSettings.CefCommandLineArgs.Add("enable-media-stream");
cefSettings.CefCommandLineArgs.Add("enable-usermedia-screen-capturing");
cefSettings.CefCommandLineArgs.Add("use-fake-ui-for-media-stream");
InitializeProxySettings(cefSettings);
logger.Debug($"Accept Language: {cefSettings.AcceptLanguageList}");
logger.Debug($"Cache Path: {cefSettings.CachePath}");
logger.Debug($"Engine Version: Chromium {Cef.ChromiumVersion}, CEF {Cef.CefVersion}, CefSharp {Cef.CefSharpVersion}");
logger.Debug($"Log File: {cefSettings.LogFile}");
@ -269,6 +371,22 @@ namespace SafeExamBrowser.Browser
return cefSettings;
}
private void InitializeIntegrityKeys()
{
logger.Debug($"Browser Exam Key (BEK) transmission is {(settings.SendBrowserExamKey ? "enabled" : "disabled")}.");
logger.Debug($"Configuration Key (CK) transmission is {(settings.SendConfigurationKey ? "enabled" : "disabled")}.");
if (settings.CustomBrowserExamKey != default)
{
keyGenerator.UseCustomBrowserExamKey(settings.CustomBrowserExamKey);
logger.Debug($"The browser application will be using a custom browser exam key.");
}
else
{
logger.Debug($"The browser application will be using the default browser exam key.");
}
}
private void InitializeProxySettings(CefSettings cefSettings)
{
if (settings.Proxy.Policy == ProxyPolicy.Custom)
@ -342,24 +460,32 @@ namespace SafeExamBrowser.Browser
throw new NotImplementedException($"Mapping for proxy protocol '{protocol}' is not yet implemented!");
}
private void Instance_PopupRequested(PopupRequestedEventArgs args)
private void Window_Closed(int id)
{
logger.Info($"Received request to create new instance{(settings.AdditionalWindow.UrlPolicy.CanLog() ? $" for '{args.Url}'" : "")}...");
CreateNewInstance(args.Url);
windows.Remove(windows.First(i => i.Id == id));
WindowsChanged?.Invoke();
logger.Info($"Window #{id} has been closed.");
}
private void Instance_ResetRequested()
private void Window_PopupRequested(PopupRequestedEventArgs args)
{
logger.Info($"Received request to create new window...");
CreateNewWindow(args);
}
private void Window_ResetRequested()
{
logger.Info("Attempting to reset browser...");
AwaitReady();
foreach (var instance in instances)
foreach (var window in windows)
{
instance.Terminated -= Instance_Terminated;
instance.Terminate();
logger.Info($"Terminated browser instance {instance.Id}.");
window.Closed -= Window_Closed;
window.Close();
logger.Info($"Closed browser window #{window.Id}.");
}
instances.Clear();
windows.Clear();
WindowsChanged?.Invoke();
if (settings.DeleteCookiesOnStartup && settings.DeleteCookiesOnShutdown)
@ -368,14 +494,8 @@ namespace SafeExamBrowser.Browser
}
nativeMethods.EmptyClipboard();
CreateNewInstance();
CreateNewWindow();
logger.Info("Successfully reset browser.");
}
private void Instance_Terminated(int id)
{
instances.Remove(instances.First(i => i.Id == id));
WindowsChanged?.Invoke();
}
}
}

View file

@ -1,141 +1,195 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Linq;
using System.Threading.Tasks;
using CefSharp;
using CefSharp.WinForms;
using SafeExamBrowser.Browser.Wrapper;
using SafeExamBrowser.Browser.Wrapper.Events;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
namespace SafeExamBrowser.Browser
{
internal class BrowserControl : ChromiumWebBrowser, IBrowserControl
internal class BrowserControl : IBrowserControl
{
private IContextMenuHandler contextMenuHandler;
private IDialogHandler dialogHandler;
private IDisplayHandler displayHandler;
private IDownloadHandler downloadHandler;
private IKeyboardHandler keyboardHandler;
private ILifeSpanHandler lifeSpanHandler;
private IRequestHandler requestHandler;
private readonly Clipboard clipboard;
private readonly ICefSharpControl control;
private readonly IDialogHandler dialogHandler;
private readonly IDisplayHandler displayHandler;
private readonly IDownloadHandler downloadHandler;
private readonly IKeyboardHandler keyboardHandler;
private readonly ILogger logger;
private readonly IRenderProcessMessageHandler renderProcessMessageHandler;
private readonly IRequestHandler requestHandler;
private AddressChangedEventHandler addressChanged;
private LoadFailedEventHandler loadFailed;
private LoadingStateChangedEventHandler loadingStateChanged;
private TitleChangedEventHandler titleChanged;
public string Address => control.Address;
public bool CanNavigateBackwards => control.IsBrowserInitialized && control.BrowserCore.CanGoBack;
public bool CanNavigateForwards => control.IsBrowserInitialized && control.BrowserCore.CanGoForward;
public object EmbeddableControl => control;
public bool CanNavigateBackwards => GetBrowser().CanGoBack;
public bool CanNavigateForwards => GetBrowser().CanGoForward;
event AddressChangedEventHandler IBrowserControl.AddressChanged
{
add { addressChanged += value; }
remove { addressChanged -= value; }
}
event LoadFailedEventHandler IBrowserControl.LoadFailed
{
add { loadFailed += value; }
remove { loadFailed -= value; }
}
event LoadingStateChangedEventHandler IBrowserControl.LoadingStateChanged
{
add { loadingStateChanged += value; }
remove { loadingStateChanged -= value; }
}
event TitleChangedEventHandler IBrowserControl.TitleChanged
{
add { titleChanged += value; }
remove { titleChanged -= value; }
}
public event AddressChangedEventHandler AddressChanged;
public event LoadFailedEventHandler LoadFailed;
public event LoadingStateChangedEventHandler LoadingStateChanged;
public event TitleChangedEventHandler TitleChanged;
public BrowserControl(
IContextMenuHandler contextMenuHandler,
Clipboard clipboard,
ICefSharpControl control,
IDialogHandler dialogHandler,
IDisplayHandler displayHandler,
IDownloadHandler downloadHandler,
IKeyboardHandler keyboardHandler,
ILifeSpanHandler lifeSpanHandler,
IRequestHandler requestHandler,
string url) : base(url)
ILogger logger,
IRenderProcessMessageHandler renderProcessMessageHandler,
IRequestHandler requestHandler)
{
this.contextMenuHandler = contextMenuHandler;
this.control = control;
this.clipboard = clipboard;
this.dialogHandler = dialogHandler;
this.displayHandler = displayHandler;
this.downloadHandler = downloadHandler;
this.keyboardHandler = keyboardHandler;
this.lifeSpanHandler = lifeSpanHandler;
this.logger = logger;
this.renderProcessMessageHandler = renderProcessMessageHandler;
this.requestHandler = requestHandler;
}
public void Destroy()
{
if (!IsDisposed)
if (!control.IsDisposed)
{
Dispose(true);
control.Dispose(true);
}
}
public void Initialize()
public void ExecuteJavaScript(string code, Action<JavaScriptResult> callback = default)
{
AddressChanged += (o, args) => addressChanged?.Invoke(args.Address);
LoadError += BrowserControl_LoadError;
LoadingStateChanged += (o, args) => loadingStateChanged?.Invoke(args.IsLoading);
TitleChanged += (o, args) => titleChanged?.Invoke(args.Title);
DialogHandler = dialogHandler;
DisplayHandler = displayHandler;
DownloadHandler = downloadHandler;
KeyboardHandler = keyboardHandler;
LifeSpanHandler = lifeSpanHandler;
MenuHandler = contextMenuHandler;
RequestHandler = requestHandler;
try
{
if (control.BrowserCore != default && control.BrowserCore.MainFrame != default)
{
control.BrowserCore.EvaluateScriptAsync(code).ContinueWith(t =>
{
callback?.Invoke(new JavaScriptResult
{
Message = t.Result.Message,
Result = t.Result.Result,
Success = t.Result.Success
});
});
}
else
{
Task.Run(() => callback?.Invoke(new JavaScriptResult
{
Message = "JavaScript can't be executed in main frame!",
Success = false
}));
}
}
catch (Exception e)
{
logger.Error($"Failed to execute JavaScript '{(code.Length > 50 ? code.Take(50) : code)}'!", e);
Task.Run(() => callback?.Invoke(new JavaScriptResult
{
Message = $"Failed to execute JavaScript '{(code.Length > 50 ? code.Take(50) : code)}'! Reason: {e.Message}",
Success = false
}));
}
}
public void Find(string term, bool isInitial, bool caseSensitive, bool forward = true)
{
this.Find(0, term, forward, caseSensitive, !isInitial);
control.Find(term, forward, caseSensitive, !isInitial);
}
public void Initialize()
{
clipboard.Changed += Clipboard_Changed;
control.AddressChanged += (o, e) => AddressChanged?.Invoke(e.Address);
control.AuthCredentialsRequired += (w, b, o, i, h, p, r, s, c, a) => a.Value = requestHandler.GetAuthCredentials(w, b, o, i, h, p, r, s, c);
control.BeforeBrowse += (w, b, f, r, u, i, a) => a.Value = requestHandler.OnBeforeBrowse(w, b, f, r, u, i);
control.BeforeDownload += (w, b, d, c) => downloadHandler.OnBeforeDownload(w, b, d, c);
control.CanDownload += (w, b, u, r, a) => a.Value = downloadHandler.CanDownload(w, b, u, r);
control.ContextCreated += (w, b, f) => renderProcessMessageHandler.OnContextCreated(w, b, f);
control.ContextReleased += (w, b, f) => renderProcessMessageHandler.OnContextReleased(w, b, f);
control.DownloadUpdated += (w, b, d, c) => downloadHandler.OnDownloadUpdated(w, b, d, c);
control.FaviconUrlChanged += (w, b, u) => displayHandler.OnFaviconUrlChange(w, b, u);
control.FileDialogRequested += (w, b, m, t, d, f, c) => dialogHandler.OnFileDialog(w, b, m, t, d, f, c);
control.FocusedNodeChanged += (w, b, f, n) => renderProcessMessageHandler.OnFocusedNodeChanged(w, b, f, n);
control.IsBrowserInitializedChanged += Control_IsBrowserInitializedChanged;
control.KeyEvent += (w, b, t, k, n, m, s) => keyboardHandler.OnKeyEvent(w, b, t, k, n, m, s);
control.LoadError += (o, e) => LoadFailed?.Invoke((int) e.ErrorCode, e.ErrorText, e.Frame.IsMain, e.FailedUrl);
control.LoadingProgressChanged += (w, b, p) => displayHandler.OnLoadingProgressChange(w, b, p);
control.LoadingStateChanged += (o, e) => LoadingStateChanged?.Invoke(e.IsLoading);
control.OpenUrlFromTab += (w, b, f, u, t, g, a) => a.Value = requestHandler.OnOpenUrlFromTab(w, b, f, u, t, g);
control.PreKeyEvent += (IWebBrowser w, IBrowser b, KeyType t, int k, int n, CefEventFlags m, bool i, ref bool s, GenericEventArgs a) => a.Value = keyboardHandler.OnPreKeyEvent(w, b, t, k, n, m, i, ref s);
control.ResourceRequestHandlerRequired += (IWebBrowser w, IBrowser b, IFrame f, IRequest r, bool n, bool d, string i, ref bool h, ResourceRequestEventArgs a) => a.Handler = requestHandler.GetResourceRequestHandler(w, b, f, r, n, d, i, ref h);
control.TitleChanged += (o, e) => TitleChanged?.Invoke(e.Title);
control.UncaughtExceptionEvent += (w, b, f, e) => renderProcessMessageHandler.OnUncaughtException(w, b, f, e);
if (control is IWebBrowser webBrowser)
{
webBrowser.JavascriptMessageReceived += WebBrowser_JavascriptMessageReceived;
}
}
public void NavigateBackwards()
{
GetBrowser().GoBack();
control.BrowserCore.GoBack();
}
public void NavigateForwards()
{
GetBrowser().GoForward();
control.BrowserCore.GoForward();
}
public void NavigateTo(string address)
{
Load(address);
control.Load(address);
}
public void ShowDeveloperConsole()
{
GetBrowser().ShowDevTools();
control.BrowserCore.ShowDevTools();
}
public void Reload()
{
GetBrowser().Reload();
control.BrowserCore.Reload();
}
public void Zoom(double level)
{
GetBrowser().SetZoomLevel(level);
control.BrowserCore.SetZoomLevel(level);
}
private void BrowserControl_LoadError(object sender, LoadErrorEventArgs e)
private void Clipboard_Changed(long id)
{
loadFailed?.Invoke((int) e.ErrorCode, e.ErrorText, e.FailedUrl);
ExecuteJavaScript($"SafeExamBrowser.clipboard.update({id}, '{clipboard.Content}');");
}
private void Control_IsBrowserInitializedChanged(object sender, EventArgs e)
{
if (control.IsBrowserInitialized)
{
control.BrowserCore.GetHost().SetFocus(true);
}
}
private void WebBrowser_JavascriptMessageReceived(object sender, JavascriptMessageReceivedEventArgs e)
{
clipboard.Process(e);
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -7,7 +7,7 @@
*/
using System;
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
namespace SafeExamBrowser.Browser
{

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -7,21 +7,25 @@
*/
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using CefSharp;
using SafeExamBrowser.Applications.Contracts;
using CefSharp.WinForms.Handler;
using CefSharp.WinForms.Host;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Browser.Filters;
using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.Browser.Wrapper;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings;
using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.Settings.Browser.Filter;
using SafeExamBrowser.UserInterface.Contracts;
@ -31,77 +35,94 @@ using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
using Syroot.Windows.IO;
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
using DisplayHandler = SafeExamBrowser.Browser.Handlers.DisplayHandler;
using Request = SafeExamBrowser.Browser.Contracts.Filters.Request;
using ResourceHandler = SafeExamBrowser.Browser.Handlers.ResourceHandler;
using TitleChangedEventHandler = SafeExamBrowser.Applications.Contracts.Events.TitleChangedEventHandler;
namespace SafeExamBrowser.Browser
{
internal class BrowserApplicationInstance : IApplicationWindow
internal class BrowserWindow : Contracts.IBrowserWindow
{
private const string CLEAR_FIND_TERM = "thisisahacktoclearthesearchresultsasitappearsthatthereisnosuchfunctionalityincef";
private const double ZOOM_FACTOR = 0.2;
private AppConfig appConfig;
private IBrowserControl control;
private readonly AppConfig appConfig;
private readonly Clipboard clipboard;
private readonly IFileSystemDialog fileSystemDialog;
private readonly IHashAlgorithm hashAlgorithm;
private readonly HttpClient httpClient;
private readonly IKeyGenerator keyGenerator;
private readonly IModuleLogger logger;
private readonly IMessageBox messageBox;
private readonly SessionMode sessionMode;
private readonly Dictionary<int, BrowserWindow> popups;
private readonly BrowserSettings settings;
private readonly string startUrl;
private readonly IText text;
private readonly IUserInterfaceFactory uiFactory;
private (string term, bool isInitial, bool caseSensitive, bool forward) findParameters;
private IBrowserWindow window;
private HttpClient httpClient;
private bool isMainInstance;
private IFileSystemDialog fileSystemDialog;
private IHashAlgorithm hashAlgorithm;
private IMessageBox messageBox;
private IModuleLogger logger;
private BrowserSettings settings;
private string startUrl;
private IText text;
private IUserInterfaceFactory uiFactory;
private double zoomLevel;
private WindowSettings WindowSettings
{
get { return isMainInstance ? settings.MainWindow : settings.AdditionalWindow; }
get { return IsMainWindow ? settings.MainWindow : settings.AdditionalWindow; }
}
internal IBrowserControl Control { get; private set; }
internal int Id { get; }
public IntPtr Handle { get; private set; }
public IconResource Icon { get; private set; }
public bool IsMainWindow { get; private set; }
public string Title { get; private set; }
public string Url { get; private set; }
internal event WindowClosedEventHandler Closed;
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
internal event LoseFocusRequestedEventHandler LoseFocusRequested;
internal event PopupRequestedEventHandler PopupRequested;
internal event ResetRequestedEventHandler ResetRequested;
internal event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
internal event InstanceTerminatedEventHandler Terminated;
internal event TerminationRequestedEventHandler TerminationRequested;
internal event UserIdentifierDetectedEventHandler UserIdentifierDetected;
public event IconChangedEventHandler IconChanged;
public event TitleChangedEventHandler TitleChanged;
public BrowserApplicationInstance(
public BrowserWindow(
AppConfig appConfig,
BrowserSettings settings,
int id,
bool isMainInstance,
Clipboard clipboard,
IFileSystemDialog fileSystemDialog,
IHashAlgorithm hashAlgorithm,
IMessageBox messageBox,
int id,
bool isMainWindow,
IKeyGenerator keyGenerator,
IModuleLogger logger,
IMessageBox messageBox,
SessionMode sessionMode,
BrowserSettings settings,
string startUrl,
IText text,
IUserInterfaceFactory uiFactory,
string startUrl)
IUserInterfaceFactory uiFactory)
{
this.appConfig = appConfig;
this.Id = id;
this.httpClient = new HttpClient();
this.isMainInstance = isMainInstance;
this.clipboard = clipboard;
this.fileSystemDialog = fileSystemDialog;
this.hashAlgorithm = hashAlgorithm;
this.messageBox = messageBox;
this.httpClient = new HttpClient();
this.Id = id;
this.IsMainWindow = isMainWindow;
this.keyGenerator = keyGenerator;
this.logger = logger;
this.messageBox = messageBox;
this.popups = new Dictionary<int, BrowserWindow>();
this.sessionMode = sessionMode;
this.settings = settings;
this.startUrl = startUrl;
this.text = text;
this.uiFactory = uiFactory;
this.startUrl = startUrl;
}
public void Activate()
@ -109,70 +130,116 @@ namespace SafeExamBrowser.Browser
window.BringToForeground();
}
internal void Initialize()
{
InitializeControl();
InitializeWindow();
}
internal void Terminate()
internal void Close()
{
window.Close();
control.Destroy();
Control.Destroy();
}
private void InitializeControl()
internal void Focus(bool forward)
{
var contextMenuHandler = new ContextMenuHandler();
if (forward)
{
window.FocusToolbar(forward);
}
else
{
window.FocusBrowser();
Activate();
}
}
internal void InitializeControl()
{
var cefSharpControl = default(ICefSharpControl);
var controlLogger = logger.CloneFor($"{nameof(BrowserControl)} #{Id}");
var dialogHandler = new DialogHandler();
var displayHandler = new DisplayHandler();
var downloadLogger = logger.CloneFor($"{nameof(DownloadHandler)} #{Id}");
var downloadHandler = new DownloadHandler(appConfig, downloadLogger, settings, WindowSettings);
var keyboardHandler = new KeyboardHandler();
var lifeSpanHandler = new LifeSpanHandler();
var renderHandler = new RenderProcessMessageHandler(appConfig, clipboard, keyGenerator, settings, text);
var requestFilter = new RequestFilter();
var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} #{Id}");
var resourceHandler = new ResourceHandler(appConfig, requestFilter, logger, settings, WindowSettings, text);
var requestHandler = new RequestHandler(appConfig, requestFilter, requestLogger, resourceHandler, settings, WindowSettings, text);
var resourceHandler = new ResourceHandler(appConfig, requestFilter, keyGenerator, logger, sessionMode, settings, WindowSettings, text);
var requestHandler = new RequestHandler(appConfig, requestFilter, requestLogger, resourceHandler, settings, WindowSettings);
Icon = new BrowserIconResource();
if (IsMainWindow)
{
cefSharpControl = new CefSharpBrowserControl(CreateLifeSpanHandlerForMainWindow(), startUrl);
}
else
{
cefSharpControl = new CefSharpPopupControl();
}
dialogHandler.DialogRequested += DialogHandler_DialogRequested;
displayHandler.FaviconChanged += DisplayHandler_FaviconChanged;
displayHandler.ProgressChanged += DisplayHandler_ProgressChanged;
downloadHandler.ConfigurationDownloadRequested += DownloadHandler_ConfigurationDownloadRequested;
downloadHandler.DownloadAborted += DownloadHandler_DownloadAborted;
downloadHandler.DownloadUpdated += DownloadHandler_DownloadUpdated;
keyboardHandler.FindRequested += KeyboardHandler_FindRequested;
keyboardHandler.FocusAddressBarRequested += KeyboardHandler_FocusAddressBarRequested;
keyboardHandler.HomeNavigationRequested += HomeNavigationRequested;
keyboardHandler.ReloadRequested += ReloadRequested;
keyboardHandler.TabPressed += KeyboardHandler_TabPressed;
keyboardHandler.ZoomInRequested += ZoomInRequested;
keyboardHandler.ZoomOutRequested += ZoomOutRequested;
keyboardHandler.ZoomResetRequested += ZoomResetRequested;
lifeSpanHandler.PopupRequested += LifeSpanHandler_PopupRequested;
resourceHandler.SessionIdentifierDetected += (id) => SessionIdentifierDetected?.Invoke(id);
requestHandler.QuitUrlVisited += RequestHandler_QuitUrlVisited;
requestHandler.RequestBlocked += RequestHandler_RequestBlocked;
resourceHandler.UserIdentifierDetected += (id) => UserIdentifierDetected?.Invoke(id);
InitializeRequestFilter(requestFilter);
control = new BrowserControl(
contextMenuHandler,
dialogHandler,
displayHandler,
downloadHandler,
keyboardHandler,
lifeSpanHandler,
requestHandler,
startUrl);
control.AddressChanged += Control_AddressChanged;
control.LoadFailed += Control_LoadFailed;
control.LoadingStateChanged += Control_LoadingStateChanged;
control.TitleChanged += Control_TitleChanged;
Control = new BrowserControl(clipboard, cefSharpControl, dialogHandler, displayHandler, downloadHandler, keyboardHandler, controlLogger, renderHandler, requestHandler);
Control.AddressChanged += Control_AddressChanged;
Control.LoadFailed += Control_LoadFailed;
Control.LoadingStateChanged += Control_LoadingStateChanged;
Control.TitleChanged += Control_TitleChanged;
control.Initialize();
Control.Initialize();
logger.Debug("Initialized browser control.");
}
internal void InitializeWindow()
{
window = uiFactory.CreateBrowserWindow(Control, settings, IsMainWindow, this.logger);
window.AddressChanged += Window_AddressChanged;
window.BackwardNavigationRequested += Window_BackwardNavigationRequested;
window.Closed += Window_Closed;
window.Closing += Window_Closing;
window.DeveloperConsoleRequested += Window_DeveloperConsoleRequested;
window.FindRequested += Window_FindRequested;
window.ForwardNavigationRequested += Window_ForwardNavigationRequested;
window.HomeNavigationRequested += HomeNavigationRequested;
window.LoseFocusRequested += Window_LoseFocusRequested;
window.ReloadRequested += ReloadRequested;
window.ZoomInRequested += ZoomInRequested;
window.ZoomOutRequested += ZoomOutRequested;
window.ZoomResetRequested += ZoomResetRequested;
window.UpdateZoomLevel(CalculateZoomPercentage());
window.Show();
window.BringToForeground();
Handle = window.Handle;
logger.Debug("Initialized browser window.");
}
private ILifeSpanHandler CreateLifeSpanHandlerForMainWindow()
{
return LifeSpanHandler
.Create(() => LifeSpanHandler_CreatePopup())
.OnBeforePopupCreated((wb, b, f, u, t, d, g, s) => LifeSpanHandler_PopupRequested(u))
.OnPopupCreated((c, u) => LifeSpanHandler_PopupCreated(c))
.OnPopupDestroyed((c, b) => LifeSpanHandler_PopupDestroyed(c))
.Build();
}
private void InitializeRequestFilter(IRequestFilter requestFilter)
{
if (settings.Filter.ProcessContentRequests || settings.Filter.ProcessMainRequests)
@ -201,31 +268,11 @@ namespace SafeExamBrowser.Browser
}
}
private void InitializeWindow()
{
window = uiFactory.CreateBrowserWindow(control, settings, isMainInstance);
window.Closing += Window_Closing;
window.AddressChanged += Window_AddressChanged;
window.BackwardNavigationRequested += Window_BackwardNavigationRequested;
window.DeveloperConsoleRequested += Window_DeveloperConsoleRequested;
window.FindRequested += Window_FindRequested;
window.ForwardNavigationRequested += Window_ForwardNavigationRequested;
window.HomeNavigationRequested += HomeNavigationRequested;
window.ReloadRequested += ReloadRequested;
window.ZoomInRequested += ZoomInRequested;
window.ZoomOutRequested += ZoomOutRequested;
window.ZoomResetRequested += ZoomResetRequested;
window.UpdateZoomLevel(CalculateZoomPercentage());
window.Show();
Handle = window.Handle;
logger.Debug("Initialized browser window.");
}
private void Control_AddressChanged(string address)
{
logger.Debug($"Navigated{(WindowSettings.UrlPolicy.CanLog() ? $" to '{address}'" : "")}.");
logger.Info($"Navigated{(WindowSettings.UrlPolicy.CanLog() ? $" to '{address}'" : "")}.");
Url = address;
window.UpdateAddress(address);
if (WindowSettings.UrlPolicy == UrlPolicy.Always || WindowSettings.UrlPolicy == UrlPolicy.BeforeTitle)
@ -234,37 +281,51 @@ namespace SafeExamBrowser.Browser
window.UpdateTitle(address);
TitleChanged?.Invoke(address);
}
AutoFind();
}
private void Control_LoadFailed(int errorCode, string errorText, string url)
private void Control_LoadFailed(int errorCode, string errorText, bool isMainRequest, string url)
{
if (errorCode == (int) CefErrorCode.None)
switch (errorCode)
{
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} was successful.");
case (int) CefErrorCode.Aborted:
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} was aborted.");
break;
case (int) CefErrorCode.InternetDisconnected:
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} has failed due to loss of internet connection.");
break;
case (int) CefErrorCode.None:
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} was successful.");
break;
case (int) CefErrorCode.UnknownUrlScheme:
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} has an unknown URL scheme and will be handled by the OS.");
break;
default:
HandleUnknownLoadFailure(errorCode, errorText, isMainRequest, url);
break;
}
else if (errorCode == (int) CefErrorCode.Aborted)
{
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} was aborted.");
}
else if (errorCode == (int) CefErrorCode.UnknownUrlScheme)
{
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} contains unknown URL scheme and will be handled by the OS.");
}
else
}
private void HandleUnknownLoadFailure(int errorCode, string errorText, bool isMainRequest, string url)
{
var requestInfo = $"{errorText} ({errorCode}, {(isMainRequest ? "main" : "resource")} request)";
logger.Warn($"Request{(WindowSettings.UrlPolicy.CanLogError() ? $" for '{url}'" : "")} failed: {requestInfo}.");
if (isMainRequest)
{
var title = text.Get(TextKey.Browser_LoadErrorTitle);
var message = text.Get(TextKey.Browser_LoadErrorMessage).Replace("%%URL%%", WindowSettings.UrlPolicy.CanLogError() ? url : "") + $" {errorText} ({errorCode})";
var message = text.Get(TextKey.Browser_LoadErrorMessage).Replace("%%URL%%", WindowSettings.UrlPolicy.CanLogError() ? url : "") + $" {requestInfo}";
logger.Warn($"Request{(WindowSettings.UrlPolicy.CanLogError() ? $" for '{url}'" : "")} failed: {errorText} ({errorCode}).");
Task.Run(() => messageBox.Show(message, title, icon: MessageBoxIcon.Error, parent: window)).ContinueWith(_ => control.NavigateBackwards());
Task.Run(() => messageBox.Show(message, title, icon: MessageBoxIcon.Error, parent: window)).ContinueWith(_ => Control.NavigateBackwards());
}
}
private void Control_LoadingStateChanged(bool isLoading)
{
window.CanNavigateBackwards = WindowSettings.AllowBackwardNavigation && control.CanNavigateBackwards;
window.CanNavigateForwards = WindowSettings.AllowForwardNavigation && control.CanNavigateForwards;
window.CanNavigateBackwards = WindowSettings.AllowBackwardNavigation && Control.CanNavigateBackwards;
window.CanNavigateForwards = WindowSettings.AllowForwardNavigation && Control.CanNavigateForwards;
window.UpdateLoadingState(isLoading);
}
@ -289,9 +350,13 @@ namespace SafeExamBrowser.Browser
{
initialPath = args.InitialPath;
}
else if (string.IsNullOrEmpty(settings.DownAndUploadDirectory))
{
initialPath = KnownFolders.Downloads.ExpandedPath;
}
else
{
initialPath = string.IsNullOrEmpty(settings.DownAndUploadDirectory) ? KnownFolders.Downloads.ExpandedPath : Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory);
initialPath = Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory);
}
if (isAllowed)
@ -302,7 +367,8 @@ namespace SafeExamBrowser.Browser
initialPath,
title: args.Title,
parent: window,
restrictNavigation: !settings.AllowCustomDownAndUploadLocation);
restrictNavigation: !settings.AllowCustomDownAndUploadLocation,
showElementPath: settings.ShowFileSystemElementPath);
if (result.Success)
{
@ -318,6 +384,7 @@ namespace SafeExamBrowser.Browser
else
{
logger.Info($"Blocked file system dialog to {args.Operation}->{args.Element}, as {(isDownload ? "downloading" : "uploading")} is not allowed.");
ShowDownUploadNotAllowedMessage(isDownload);
}
}
@ -367,6 +434,11 @@ namespace SafeExamBrowser.Browser
}
}
private void DownloadHandler_DownloadAborted()
{
ShowDownUploadNotAllowedMessage();
}
private void DownloadHandler_DownloadUpdated(DownloadItemState state)
{
window.UpdateDownloadState(state);
@ -374,7 +446,7 @@ namespace SafeExamBrowser.Browser
private void HomeNavigationRequested()
{
if (isMainInstance && (settings.UseStartUrlAsHomeUrl || !string.IsNullOrWhiteSpace(settings.HomeUrl)))
if (IsMainWindow && (settings.UseStartUrlAsHomeUrl || !string.IsNullOrWhiteSpace(settings.HomeUrl)))
{
var navigate = false;
var url = settings.UseStartUrlAsHomeUrl ? settings.StartUrl : settings.HomeUrl;
@ -411,7 +483,7 @@ namespace SafeExamBrowser.Browser
if (navigate)
{
control.NavigateTo(url);
Control.NavigateTo(url);
}
}
}
@ -424,32 +496,91 @@ namespace SafeExamBrowser.Browser
}
}
private void LifeSpanHandler_PopupRequested(PopupRequestedEventArgs args)
private void KeyboardHandler_FocusAddressBarRequested()
{
var validCurrentUri = Uri.TryCreate(control.Address, UriKind.Absolute, out var currentUri);
var validNewUri = Uri.TryCreate(args.Url, UriKind.Absolute, out var newUri);
window.FocusAddressBar();
}
private void KeyboardHandler_TabPressed(bool shiftPressed)
{
Control.ExecuteJavaScript("document.activeElement.tagName", result =>
{
if (result.Result is string tagName && tagName?.ToUpper() == "BODY")
{
// This means the user is now at the start of the focus / tabIndex chain in the website.
if (shiftPressed)
{
window.FocusToolbar(!shiftPressed);
}
else
{
LoseFocusRequested?.Invoke(true);
}
}
});
}
private ChromiumHostControl LifeSpanHandler_CreatePopup()
{
var args = new PopupRequestedEventArgs();
PopupRequested?.Invoke(args);
var control = args.Window.Control.EmbeddableControl as ChromiumHostControl;
var id = control.GetHashCode();
var window = args.Window;
popups[id] = window;
window.Closed += (_) => popups.Remove(id);
return control;
}
private void LifeSpanHandler_PopupCreated(ChromiumHostControl control)
{
var id = control.GetHashCode();
var window = popups[id];
window.InitializeWindow();
}
private void LifeSpanHandler_PopupDestroyed(ChromiumHostControl control)
{
var id = control.GetHashCode();
var window = popups[id];
window.Close();
}
private PopupCreation LifeSpanHandler_PopupRequested(string targetUrl)
{
var creation = PopupCreation.Cancel;
var validCurrentUri = Uri.TryCreate(Control.Address, UriKind.Absolute, out var currentUri);
var validNewUri = Uri.TryCreate(targetUrl, UriKind.Absolute, out var newUri);
var sameHost = validCurrentUri && validNewUri && string.Equals(currentUri.Host, newUri.Host, StringComparison.OrdinalIgnoreCase);
switch (settings.PopupPolicy)
{
case PopupPolicy.Allow:
case PopupPolicy.AllowSameHost when sameHost:
logger.Debug($"Forwarding request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{args.Url}'" : "")}...");
PopupRequested?.Invoke(args);
logger.Debug($"Forwarding request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{targetUrl}'" : "")}...");
creation = PopupCreation.Continue;
break;
case PopupPolicy.AllowSameWindow:
case PopupPolicy.AllowSameHostAndWindow when sameHost:
logger.Info($"Discarding request to open new window and loading{(WindowSettings.UrlPolicy.CanLog() ? $" '{args.Url}'" : "")} directly...");
control.NavigateTo(args.Url);
logger.Info($"Discarding request to open new window and loading{(WindowSettings.UrlPolicy.CanLog() ? $" '{targetUrl}'" : "")} directly...");
Control.NavigateTo(targetUrl);
break;
case PopupPolicy.AllowSameHost when !sameHost:
case PopupPolicy.AllowSameHostAndWindow when !sameHost:
logger.Info($"Blocked request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{args.Url}'" : "")} as it targets a different host.");
logger.Info($"Blocked request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{targetUrl}'" : "")} as it targets a different host.");
break;
default:
logger.Info($"Blocked request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{args.Url}'" : "")}.");
logger.Info($"Blocked request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{targetUrl}'" : "")}.");
break;
}
return creation;
}
private void RequestHandler_QuitUrlVisited(string url)
@ -496,7 +627,7 @@ namespace SafeExamBrowser.Browser
var message = text.Get(TextKey.MessageBox_BrowserNavigationBlocked).Replace("%%URL%%", WindowSettings.UrlPolicy.CanLogError() ? url : "");
var title = text.Get(TextKey.MessageBox_BrowserNavigationBlockedTitle);
control.TitleChanged -= Control_TitleChanged;
Control.TitleChanged -= Control_TitleChanged;
if (url.Equals(startUrl, StringComparison.OrdinalIgnoreCase))
{
@ -505,7 +636,7 @@ namespace SafeExamBrowser.Browser
}
messageBox.Show(message, title, parent: window);
control.TitleChanged += Control_TitleChanged;
Control.TitleChanged += Control_TitleChanged;
});
}
@ -518,7 +649,7 @@ namespace SafeExamBrowser.Browser
if (result == MessageBoxResult.Yes)
{
logger.Debug("The user confirmed reloading the current page...");
control.Reload();
Control.Reload();
}
else
{
@ -528,7 +659,7 @@ namespace SafeExamBrowser.Browser
else if (WindowSettings.AllowReloading)
{
logger.Debug("Reloading current page...");
control.Reload();
Control.Reload();
}
else
{
@ -536,6 +667,14 @@ namespace SafeExamBrowser.Browser
}
}
private void ShowDownUploadNotAllowedMessage(bool isDownload = true)
{
var message = isDownload ? TextKey.MessageBox_DownloadNotAllowed : TextKey.MessageBox_UploadNotAllowed;
var title = isDownload ? TextKey.MessageBox_DownloadNotAllowedTitle : TextKey.MessageBox_UploadNotAllowedTitle;
messageBox.Show(message, title, icon: MessageBoxIcon.Warning, parent: window);
}
private void Window_AddressChanged(string address)
{
var isValid = Uri.TryCreate(address, UriKind.Absolute, out _) || Uri.TryCreate($"https://{address}", UriKind.Absolute, out _);
@ -543,7 +682,7 @@ namespace SafeExamBrowser.Browser
if (isValid)
{
logger.Debug($"The user requested to navigate to '{address}', the URI is valid.");
control.NavigateTo(address);
Control.NavigateTo(address);
}
else
{
@ -555,34 +694,49 @@ namespace SafeExamBrowser.Browser
private void Window_BackwardNavigationRequested()
{
logger.Debug("Navigating backwards...");
control.NavigateBackwards();
Control.NavigateBackwards();
}
private void Window_Closing()
{
logger.Info($"Instance has terminated.");
control.Destroy();
Terminated?.Invoke(Id);
logger.Debug($"Window is closing...");
}
private void Window_Closed()
{
logger.Debug($"Window has been closed.");
Control.Destroy();
Closed?.Invoke(Id);
}
private void Window_DeveloperConsoleRequested()
{
logger.Debug("Showing developer console...");
control.ShowDeveloperConsole();
Control.ShowDeveloperConsole();
}
private void Window_FindRequested(string term, bool isInitial, bool caseSensitive, bool forward = true)
{
if (settings.AllowFind)
{
control.Find(term, isInitial, caseSensitive, forward);
findParameters.caseSensitive = caseSensitive;
findParameters.forward = forward;
findParameters.isInitial = isInitial;
findParameters.term = term;
Control.Find(term, isInitial, caseSensitive, forward);
}
}
private void Window_ForwardNavigationRequested()
{
logger.Debug("Navigating forwards...");
control.NavigateForwards();
Control.NavigateForwards();
}
private void Window_LoseFocusRequested(bool forward)
{
LoseFocusRequested?.Invoke(forward);
}
private void ZoomInRequested()
@ -590,7 +744,7 @@ namespace SafeExamBrowser.Browser
if (settings.AllowPageZoom && CalculateZoomPercentage() < 300)
{
zoomLevel += ZOOM_FACTOR;
control.Zoom(zoomLevel);
Control.Zoom(zoomLevel);
window.UpdateZoomLevel(CalculateZoomPercentage());
logger.Debug($"Increased page zoom to {CalculateZoomPercentage()}%.");
}
@ -601,7 +755,7 @@ namespace SafeExamBrowser.Browser
if (settings.AllowPageZoom && CalculateZoomPercentage() > 25)
{
zoomLevel -= ZOOM_FACTOR;
control.Zoom(zoomLevel);
Control.Zoom(zoomLevel);
window.UpdateZoomLevel(CalculateZoomPercentage());
logger.Debug($"Decreased page zoom to {CalculateZoomPercentage()}%.");
}
@ -612,12 +766,20 @@ namespace SafeExamBrowser.Browser
if (settings.AllowPageZoom)
{
zoomLevel = 0;
control.Zoom(0);
Control.Zoom(0);
window.UpdateZoomLevel(CalculateZoomPercentage());
logger.Debug($"Reset page zoom to {CalculateZoomPercentage()}%.");
}
}
private void AutoFind()
{
if (settings.AllowFind && !string.IsNullOrEmpty(findParameters.term) && !CLEAR_FIND_TERM.Equals(findParameters.term, StringComparison.OrdinalIgnoreCase))
{
Control.Find(findParameters.term, findParameters.isInitial, findParameters.caseSensitive, findParameters.forward);
}
}
private double CalculateZoomPercentage()
{
return (zoomLevel * 25.0) + 100.0;

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Threading.Tasks;
using CefSharp;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Logging.Contracts;
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
namespace SafeExamBrowser.Browser
{
internal class Clipboard
{
private readonly ILogger logger;
private readonly BrowserSettings settings;
internal string Content { get; private set; }
internal event ClipboardChangedEventHandler Changed;
internal Clipboard(ILogger logger, BrowserSettings settings)
{
this.logger = logger;
this.settings = settings;
}
internal void Process(JavascriptMessageReceivedEventArgs message)
{
if (settings.UseIsolatedClipboard)
{
try
{
var data = message.ConvertMessageTo<Data>();
if (data != default && data.Type == "Clipboard" && TrySetContent(data.Content))
{
Task.Run(() => Changed?.Invoke(data.Id));
}
}
catch (Exception e)
{
logger.Error($"Failed to process browser message '{message}'!", e);
}
}
}
private bool TrySetContent(object value)
{
var text = value as string;
if (text != default)
{
Content = text;
}
return text != default;
}
private class Data
{
public string Content { get; set; }
public long Id { get; set; }
public string Type { get; set; }
}
}
}

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
SafeExamBrowser = {
version: 'SEB_Windows_%%_VERSION_%%',
security: {
browserExamKey: '%%_BEK_%%',
configKey: '%%_CK_%%',
updateKeys: (callback) => callback()
}
}

View file

@ -0,0 +1,195 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Original code taken and slightly adapted from https://github.com/eqsoft/seb2/blob/master/browser/app/modules/SebBrowser.jsm#L1215.
*/
SafeExamBrowser.clipboard = {
id: Math.round((Date.now() + Math.random()) * 1000),
ranges: [],
text: "",
clear: function () {
this.ranges = [];
this.text = "";
},
getContentEncoded: function () {
var bytes = new TextEncoder().encode(this.text);
var base64 = btoa(String.fromCodePoint(...bytes));
return base64;
},
update: function (id, base64) {
if (this.id != id) {
var bytes = Uint8Array.from(atob(base64), (m) => m.codePointAt(0));
var content = new TextDecoder().decode(bytes);
this.ranges = [];
this.text = content;
}
}
}
function copySelectedData(e) {
if (e.target.contentEditable && e.target.setRangeText) {
SafeExamBrowser.clipboard.text = e.target.value.substring(e.target.selectionStart, e.target.selectionEnd);
SafeExamBrowser.clipboard.ranges = [];
} else {
var selection = e.target.ownerDocument.defaultView.getSelection();
var text = "";
for (var i = 0; i < selection.rangeCount; i++) {
SafeExamBrowser.clipboard.ranges[i] = selection.getRangeAt(i).cloneContents();
text += SafeExamBrowser.clipboard.ranges[i].textContent;
}
SafeExamBrowser.clipboard.text = text;
}
}
function cutSelectedData(e) {
if (e.target.contentEditable && e.target.setRangeText) {
e.target.setRangeText("", e.target.selectionStart, e.target.selectionEnd, 'select');
} else {
var designMode = e.target.ownerDocument.designMode;
var contentEditables = e.target.ownerDocument.querySelectorAll('*[contenteditable]');
var selection = e.target.ownerDocument.defaultView.getSelection();
for (var i = 0; i < selection.rangeCount; i++) {
var range = selection.getRangeAt(i);
if (designMode === 'on') {
range.deleteContents();
} else {
if (contentEditables.length) {
contentEditables.forEach(node => {
if (node.contains(range.commonAncestorContainer)) {
range.deleteContents();
}
});
}
}
}
}
}
function pasteSelectedData(e) {
if (e.target.contentEditable && e.target.setRangeText) {
e.target.setRangeText("", e.target.selectionStart, e.target.selectionEnd, 'select');
e.target.setRangeText(SafeExamBrowser.clipboard.text, e.target.selectionStart, e.target.selectionStart + SafeExamBrowser.clipboard.text.length, 'end');
} else {
var w = e.target.ownerDocument.defaultView;
var designMode = e.target.ownerDocument.designMode;
var contentEditables = e.target.ownerDocument.querySelectorAll('*[contenteditable]');
var selection = w.getSelection();
for (var i = 0; i < selection.rangeCount; i++) {
var r = selection.getRangeAt(i);
if (designMode === 'on') {
r.deleteContents();
} else {
if (contentEditables.length) {
contentEditables.forEach(node => {
if (node.contains(r.commonAncestorContainer)) {
r.deleteContents();
}
});
}
}
}
if (designMode === 'on') {
var range = w.getSelection().getRangeAt(0);
if (SafeExamBrowser.clipboard.ranges.length > 0) {
SafeExamBrowser.clipboard.ranges.map(r => {
range = w.getSelection().getRangeAt(0);
range.collapse();
const newNode = r.cloneNode(true);
range.insertNode(newNode);
range.collapse();
});
} else {
range.collapse();
range.insertNode(w.document.createTextNode(SafeExamBrowser.clipboard.text));
range.collapse();
}
} else {
if (contentEditables.length) {
contentEditables.forEach(node => {
var range = w.getSelection().getRangeAt(0);
if (node.contains(range.commonAncestorContainer)) {
if (SafeExamBrowser.clipboard.ranges.length > 0) {
SafeExamBrowser.clipboard.ranges.map(r => {
range = w.getSelection().getRangeAt(0);
range.collapse();
const newNode = r.cloneNode(true);
range.insertNode(newNode);
range.collapse();
});
} else {
range = w.getSelection().getRangeAt(0);
range.collapse();
range.insertNode(w.document.createTextNode(SafeExamBrowser.clipboard.text));
range.collapse();
}
}
});
}
}
}
}
function onCopy(e) {
SafeExamBrowser.clipboard.clear();
try {
copySelectedData(e);
CefSharp.PostMessage({ Type: "Clipboard", Id: SafeExamBrowser.clipboard.id, Content: SafeExamBrowser.clipboard.getContentEncoded() });
} finally {
e.preventDefault();
e.returnValue = false;
}
return false;
}
function onCut(e) {
SafeExamBrowser.clipboard.clear();
try {
copySelectedData(e);
cutSelectedData(e);
CefSharp.PostMessage({ Type: "Clipboard", Id: SafeExamBrowser.clipboard.id, Content: SafeExamBrowser.clipboard.getContentEncoded() });
} finally {
e.preventDefault();
e.returnValue = false;
}
return false;
}
function onPaste(e) {
try {
pasteSelectedData(e);
} finally {
e.preventDefault();
e.returnValue = false;
}
return false;
}
window.document.addEventListener("copy", onCopy, true);
window.document.addEventListener("cut", onCut, true);
window.document.addEventListener("paste", onPaste, true);

View file

@ -0,0 +1,119 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.IO;
using System.Reflection;
using SafeExamBrowser.I18n.Contracts;
namespace SafeExamBrowser.Browser.Content
{
internal class ContentLoader
{
private readonly IText text;
private string api;
private string clipboard;
private string pageZoom;
internal ContentLoader(IText text)
{
this.text = text;
}
internal string LoadApi(string browserExamKey, string configurationKey, string version)
{
if (api == default)
{
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
var path = $"{typeof(ContentLoader).Namespace}.Api.js";
using (var stream = assembly.GetManifestResourceStream(path))
using (var reader = new StreamReader(stream))
{
api = reader.ReadToEnd();
}
}
var js = api;
js = js.Replace("%%_BEK_%%", browserExamKey);
js = js.Replace("%%_CK_%%", configurationKey);
js = js.Replace("%%_VERSION_%%", version);
return js;
}
internal string LoadBlockedContent()
{
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
var path = $"{typeof(ContentLoader).Namespace}.BlockedContent.html";
using (var stream = assembly.GetManifestResourceStream(path))
using (var reader = new StreamReader(stream))
{
var html = reader.ReadToEnd();
html = html.Replace("%%MESSAGE%%", text.Get(TextKey.Browser_BlockedContentMessage));
return html;
}
}
internal string LoadBlockedPage()
{
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
var path = $"{typeof(ContentLoader).Namespace}.BlockedPage.html";
using (var stream = assembly.GetManifestResourceStream(path))
using (var reader = new StreamReader(stream))
{
var html = reader.ReadToEnd();
html = html.Replace("%%BACK_BUTTON%%", text.Get(TextKey.Browser_BlockedPageButton));
html = html.Replace("%%MESSAGE%%", text.Get(TextKey.Browser_BlockedPageMessage));
html = html.Replace("%%TITLE%%", text.Get(TextKey.Browser_BlockedPageTitle));
return html;
}
}
internal string LoadClipboard()
{
if (clipboard == default)
{
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
var path = $"{typeof(ContentLoader).Namespace}.Clipboard.js";
using (var stream = assembly.GetManifestResourceStream(path))
using (var reader = new StreamReader(stream))
{
clipboard = reader.ReadToEnd();
}
}
return clipboard;
}
internal string LoadPageZoom()
{
if (pageZoom == default)
{
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
var path = $"{typeof(ContentLoader).Namespace}.PageZoom.js";
using (var stream = assembly.GetManifestResourceStream(path))
using (var reader = new StreamReader(stream))
{
pageZoom = reader.ReadToEnd();
}
}
return pageZoom;
}
}
}

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
function disableMouseWheelZoom(e) {
if (e.ctrlKey) {
e.preventDefault();
e.stopPropagation();
}
}
document.addEventListener('wheel', disableMouseWheelZoom, { passive: false });

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -8,5 +8,5 @@
namespace SafeExamBrowser.Browser.Events
{
internal delegate void InstanceTerminatedEventHandler(int id);
internal delegate void ClipboardChangedEventHandler(long id);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -0,0 +1,12 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Browser.Events
{
internal delegate void DownloadAbortedEventHandler();
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -10,6 +10,6 @@ namespace SafeExamBrowser.Browser.Events
{
internal class PopupRequestedEventArgs
{
public string Url { get; set; }
public BrowserWindow Window { get; set; }
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -0,0 +1,12 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Browser.Events
{
internal delegate void WindowClosedEventHandler(int id);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -10,7 +10,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using CefSharp;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.Browser.Wrapper;
namespace SafeExamBrowser.Browser.Handlers
{
@ -18,13 +18,13 @@ namespace SafeExamBrowser.Browser.Handlers
{
internal event DialogRequestedEventHandler DialogRequested;
public bool OnFileDialog(IWebBrowser webBrowser, IBrowser browser, CefFileDialogMode mode, CefFileDialogFlags flags, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback)
public bool OnFileDialog(IWebBrowser webBrowser, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, IFileDialogCallback callback)
{
var args = new DialogRequestedEventArgs
{
Element = ToElement(mode),
Element = mode.ToElement(),
InitialPath = defaultFilePath,
Operation = ToOperation(mode),
Operation = mode.ToOperation(),
Title = title
};
@ -36,7 +36,7 @@ namespace SafeExamBrowser.Browser.Handlers
{
if (args.Success)
{
callback.Continue(selectedAcceptFilter, new List<string> { args.FullPath });
callback.Continue(new List<string> { args.FullPath });
}
else
{
@ -47,27 +47,5 @@ namespace SafeExamBrowser.Browser.Handlers
return true;
}
private FileSystemElement ToElement(CefFileDialogMode mode)
{
switch (mode)
{
case CefFileDialogMode.OpenFolder:
return FileSystemElement.Folder;
default:
return FileSystemElement.File;
}
}
private FileSystemOperation ToOperation(CefFileDialogMode mode)
{
switch (mode)
{
case CefFileDialogMode.Save:
return FileSystemOperation.Save;
default:
return FileSystemOperation.Open;
}
}
}
}

View file

@ -1,14 +1,16 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using CefSharp;
using CefSharp.Enums;
using CefSharp.Structs;
using SafeExamBrowser.Browser.Events;
@ -33,6 +35,11 @@ namespace SafeExamBrowser.Browser.Handlers
return false;
}
public bool OnCursorChange(IWebBrowser chromiumWebBrowser, IBrowser browser, IntPtr cursor, CursorType type, CursorInfo customCursorInfo)
{
return false;
}
public void OnFaviconUrlChange(IWebBrowser chromiumWebBrowser, IBrowser browser, IList<string> urls)
{
if (urls.Any())

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -24,14 +24,15 @@ namespace SafeExamBrowser.Browser.Handlers
{
internal class DownloadHandler : IDownloadHandler
{
private AppConfig appConfig;
private BrowserSettings settings;
private WindowSettings windowSettings;
private ConcurrentDictionary<int, DownloadFinishedCallback> callbacks;
private ConcurrentDictionary<int, Guid> downloads;
private ILogger logger;
private readonly AppConfig appConfig;
private readonly ConcurrentDictionary<int, DownloadFinishedCallback> callbacks;
private readonly ConcurrentDictionary<int, Guid> downloads;
private readonly ILogger logger;
private readonly BrowserSettings settings;
private readonly WindowSettings windowSettings;
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
internal event DownloadAbortedEventHandler DownloadAborted;
internal event DownloadUpdatedEventHandler DownloadUpdated;
internal DownloadHandler(AppConfig appConfig, ILogger logger, BrowserSettings settings, WindowSettings windowSettings)
@ -44,18 +45,33 @@ namespace SafeExamBrowser.Browser.Handlers
this.windowSettings = windowSettings;
}
public bool CanDownload(IWebBrowser chromiumWebBrowser, IBrowser browser, string url, string requestMethod)
{
return true;
}
public void OnBeforeDownload(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback)
{
var uri = new Uri(downloadItem.Url);
var uriExtension = Path.GetExtension(uri.AbsolutePath);
var fileExtension = Path.GetExtension(downloadItem.SuggestedFileName);
var isConfigurationFile = false;
var url = downloadItem.Url;
var urlExtension = default(string);
if (downloadItem.Url.StartsWith("data:"))
{
url = downloadItem.Url.Length <= 100 ? downloadItem.Url : downloadItem.Url.Substring(0, 100) + "...";
}
if (Uri.TryCreate(downloadItem.Url, UriKind.RelativeOrAbsolute, out var uri))
{
urlExtension = Path.GetExtension(uri.AbsolutePath);
}
isConfigurationFile |= string.Equals(appConfig.ConfigurationFileExtension, fileExtension, StringComparison.OrdinalIgnoreCase);
isConfigurationFile |= string.Equals(appConfig.ConfigurationFileExtension, uriExtension, StringComparison.OrdinalIgnoreCase);
isConfigurationFile |= string.Equals(appConfig.ConfigurationFileExtension, urlExtension, StringComparison.OrdinalIgnoreCase);
isConfigurationFile |= string.Equals(appConfig.ConfigurationFileMimeType, downloadItem.MimeType, StringComparison.OrdinalIgnoreCase);
logger.Debug($"Detected download request{(windowSettings.UrlPolicy.CanLog() ? $" for '{uri}'" : "")}.");
logger.Debug($"Detected download request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")}.");
if (isConfigurationFile)
{
@ -67,7 +83,8 @@ namespace SafeExamBrowser.Browser.Handlers
}
else
{
logger.Info($"Aborted download request{(windowSettings.UrlPolicy.CanLog() ? $" for '{uri}'" : "")}, as downloading is not allowed.");
logger.Info($"Aborted download request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")}, as downloading is not allowed.");
Task.Run(() => DownloadAborted?.Invoke());
}
}
@ -85,7 +102,7 @@ namespace SafeExamBrowser.Browser.Handlers
IsComplete = downloadItem.IsComplete,
Url = downloadItem.Url
};
Task.Run(() => DownloadUpdated?.Invoke(state));
}
@ -93,7 +110,7 @@ namespace SafeExamBrowser.Browser.Handlers
{
logger.Debug($"Download of '{downloadItem.FullPath}' {(downloadItem.IsComplete ? "is complete" : "was cancelled")}.");
if (callbacks.TryRemove(downloadItem.Id, out DownloadFinishedCallback finished) && finished != null)
if (callbacks.TryRemove(downloadItem.Id, out var finished) && finished != null)
{
Task.Run(() => finished.Invoke(downloadItem.IsComplete, downloadItem.Url, downloadItem.FullPath));
}
@ -121,6 +138,11 @@ namespace SafeExamBrowser.Browser.Handlers
filePath = Path.Combine(KnownFolders.Downloads.ExpandedPath, downloadItem.SuggestedFileName);
}
if (File.Exists(filePath))
{
filePath = AppendIndexSuffixTo(filePath);
}
if (showDialog)
{
logger.Debug($"Allowing user to select custom download location, with '{filePath}' as suggestion.");
@ -138,6 +160,26 @@ namespace SafeExamBrowser.Browser.Handlers
}
}
private string AppendIndexSuffixTo(string filePath)
{
var directory = Path.GetDirectoryName(filePath);
var extension = Path.GetExtension(filePath);
var name = Path.GetFileNameWithoutExtension(filePath);
var path = default(string);
for (var suffix = 1; suffix < int.MaxValue; suffix++)
{
path = Path.Combine(directory, $"{name}({suffix}){extension}");
if (!File.Exists(path))
{
break;
}
}
return path;
}
private void RequestConfigurationFileDownload(DownloadItem downloadItem, IBeforeDownloadCallback callback)
{
var args = new DownloadEventArgs { Url = downloadItem.Url };

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -8,6 +8,7 @@
using System.Windows.Forms;
using CefSharp;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.UserInterface.Contracts;
namespace SafeExamBrowser.Browser.Handlers
@ -20,6 +21,10 @@ namespace SafeExamBrowser.Browser.Handlers
internal event ActionRequestedEventHandler ZoomInRequested;
internal event ActionRequestedEventHandler ZoomOutRequested;
internal event ActionRequestedEventHandler ZoomResetRequested;
internal event ActionRequestedEventHandler FocusAddressBarRequested;
internal event TabPressedEventHandler TabPressed;
private int? currentKeyDown = null;
public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int keyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey)
{
@ -38,7 +43,12 @@ namespace SafeExamBrowser.Browser.Handlers
HomeNavigationRequested?.Invoke();
}
if ((ctrl && keyCode == (int) Keys.Add) || (ctrl && shift && keyCode == (int) Keys.D1))
if (ctrl && keyCode == (int) Keys.L)
{
FocusAddressBarRequested?.Invoke();
}
if ((ctrl && keyCode == (int) Keys.Add) || (ctrl && keyCode == (int) Keys.Oemplus) || (ctrl && shift && keyCode == (int) Keys.D1))
{
ZoomInRequested?.Invoke();
}
@ -52,8 +62,14 @@ namespace SafeExamBrowser.Browser.Handlers
{
ZoomResetRequested?.Invoke();
}
if (keyCode == (int) Keys.Tab && keyCode == currentKeyDown)
{
TabPressed?.Invoke(shift);
}
}
currentKeyDown = null;
return false;
}
@ -66,6 +82,11 @@ namespace SafeExamBrowser.Browser.Handlers
return true;
}
if (type == KeyType.RawKeyDown || type == KeyType.KeyDown)
{
currentKeyDown = keyCode;
}
return false;
}
}

View file

@ -1,41 +0,0 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using CefSharp;
using SafeExamBrowser.Browser.Events;
namespace SafeExamBrowser.Browser.Handlers
{
internal class LifeSpanHandler : ILifeSpanHandler
{
public event PopupRequestedEventHandler PopupRequested;
public bool DoClose(IWebBrowser chromiumWebBrowser, IBrowser browser)
{
return false;
}
public void OnAfterCreated(IWebBrowser chromiumWebBrowser, IBrowser browser)
{
}
public void OnBeforeClose(IWebBrowser chromiumWebBrowser, IBrowser browser)
{
}
public bool OnBeforePopup(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser)
{
var args = new PopupRequestedEventArgs { Url = targetUrl };
newBrowser = default(IWebBrowser);
PopupRequested?.Invoke(args);
return true;
}
}
}

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using CefSharp;
using SafeExamBrowser.Browser.Content;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.I18n.Contracts;
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
namespace SafeExamBrowser.Browser.Handlers
{
internal class RenderProcessMessageHandler : IRenderProcessMessageHandler
{
private readonly AppConfig appConfig;
private readonly Clipboard clipboard;
private readonly ContentLoader contentLoader;
private readonly IKeyGenerator keyGenerator;
private readonly BrowserSettings settings;
private readonly IText text;
internal RenderProcessMessageHandler(AppConfig appConfig, Clipboard clipboard, IKeyGenerator keyGenerator, BrowserSettings settings, IText text)
{
this.appConfig = appConfig;
this.clipboard = clipboard;
this.contentLoader = new ContentLoader(text);
this.keyGenerator = keyGenerator;
this.settings = settings;
this.text = text;
}
public void OnContextCreated(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)
{
var browserExamKey = keyGenerator.CalculateBrowserExamKeyHash(settings.ConfigurationKey, settings.BrowserExamKeySalt, frame.Url);
var configurationKey = keyGenerator.CalculateConfigurationKeyHash(settings.ConfigurationKey, frame.Url);
var api = contentLoader.LoadApi(browserExamKey, configurationKey, appConfig.ProgramBuildVersion);
var clipboardScript = contentLoader.LoadClipboard();
var pageZoomScript = contentLoader.LoadPageZoom();
frame.ExecuteJavaScriptAsync(api);
if (!settings.AllowPageZoom)
{
frame.ExecuteJavaScriptAsync(pageZoomScript);
}
if (!settings.AllowPrint)
{
frame.ExecuteJavaScriptAsync($"window.print = function() {{ alert('{text.Get(TextKey.Browser_PrintNotAllowed)}') }}");
}
if (settings.UseIsolatedClipboard)
{
frame.ExecuteJavaScriptAsync(clipboardScript);
if (clipboard.Content != default)
{
frame.ExecuteJavaScriptAsync($"SafeExamBrowser.clipboard.update('', '{clipboard.Content}');");
}
}
}
public void OnContextReleased(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)
{
}
public void OnFocusedNodeChanged(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IDomNode node)
{
}
public void OnUncaughtException(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, JavascriptException exception)
{
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -7,12 +7,13 @@
*/
using System;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using CefSharp;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.Settings.Browser.Filter;
@ -23,12 +24,14 @@ namespace SafeExamBrowser.Browser.Handlers
{
internal class RequestHandler : CefSharp.Handler.RequestHandler
{
private IRequestFilter filter;
private ILogger logger;
private readonly AppConfig appConfig;
private readonly IRequestFilter filter;
private readonly ILogger logger;
private readonly ResourceHandler resourceHandler;
private readonly WindowSettings windowSettings;
private readonly BrowserSettings settings;
private string quitUrlPattern;
private ResourceHandler resourceHandler;
private WindowSettings windowSettings;
private BrowserSettings settings;
internal event UrlEventHandler QuitUrlVisited;
internal event UrlEventHandler RequestBlocked;
@ -39,9 +42,9 @@ namespace SafeExamBrowser.Browser.Handlers
ILogger logger,
ResourceHandler resourceHandler,
BrowserSettings settings,
WindowSettings windowSettings,
IText text)
WindowSettings windowSettings)
{
this.appConfig = appConfig;
this.filter = filter;
this.logger = logger;
this.resourceHandler = resourceHandler;
@ -91,18 +94,79 @@ namespace SafeExamBrowser.Browser.Handlers
return true;
}
if (IsConfigurationFile(request, out var downloadUrl))
{
browser.GetHost().StartDownload(downloadUrl);
return true;
}
return base.OnBeforeBrowse(webBrowser, browser, frame, request, userGesture, isRedirect);
}
protected override bool OnOpenUrlFromTab(IWebBrowser webBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture)
{
switch (targetDisposition)
{
case WindowOpenDisposition.NewBackgroundTab:
case WindowOpenDisposition.NewPopup:
case WindowOpenDisposition.NewWindow:
case WindowOpenDisposition.SaveToDisk:
return true;
default:
return base.OnOpenUrlFromTab(webBrowser, browser, frame, targetUrl, targetDisposition, userGesture);
}
}
private bool IsConfigurationFile(IRequest request, out string downloadUrl)
{
var isValidUri = Uri.TryCreate(request.Url, UriKind.RelativeOrAbsolute, out var uri);
var hasFileExtension = string.Equals(appConfig.ConfigurationFileExtension, Path.GetExtension(uri.AbsolutePath), StringComparison.OrdinalIgnoreCase);
var isDataUri = request.Url.Contains(appConfig.ConfigurationFileMimeType);
var isConfigurationFile = isValidUri && (hasFileExtension || isDataUri);
downloadUrl = request.Url;
if (isConfigurationFile)
{
if (isDataUri)
{
if (uri.Scheme == appConfig.SebUriScheme)
{
downloadUrl = request.Url.Replace($"{appConfig.SebUriScheme}{Uri.SchemeDelimiter}", "data:");
}
else if (uri.Scheme == appConfig.SebUriSchemeSecure)
{
downloadUrl = request.Url.Replace($"{appConfig.SebUriSchemeSecure}{Uri.SchemeDelimiter}", "data:");
}
}
else
{
if (uri.Scheme == appConfig.SebUriScheme)
{
downloadUrl = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri.AbsoluteUri;
}
else if (uri.Scheme == appConfig.SebUriSchemeSecure)
{
downloadUrl = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttps }.Uri.AbsoluteUri;
}
}
logger.Debug($"Detected configuration file {(windowSettings.UrlPolicy.CanLog() ? $"'{uri}'" : "")}.");
}
return isConfigurationFile;
}
private bool IsQuitUrl(IRequest request)
{
var isQuitUrl = false;
if (!string.IsNullOrWhiteSpace(settings.QuitUrl))
{
if (quitUrlPattern == default(string))
if (quitUrlPattern == default)
{
quitUrlPattern = Regex.Escape(settings.QuitUrl.TrimEnd('/')) + @"\/?";
quitUrlPattern = $"^{Regex.Escape(settings.QuitUrl.TrimEnd('/'))}/?$";
}
isQuitUrl = Regex.IsMatch(request.Url, quitUrlPattern, RegexOptions.IgnoreCase);
@ -119,29 +183,37 @@ namespace SafeExamBrowser.Browser.Handlers
private bool Block(IRequest request)
{
var block = false;
var url = WebUtility.UrlDecode(request.Url);
var isValidUrl = Uri.TryCreate(url, UriKind.Absolute, out _);
if (settings.Filter.ProcessMainRequests && request.ResourceType == ResourceType.MainFrame)
if (settings.Filter.ProcessMainRequests && request.ResourceType == ResourceType.MainFrame && isValidUrl)
{
var result = filter.Process(new Request { Url = request.Url });
var result = filter.Process(new Request { Url = url });
if (result == FilterResult.Block)
// We apparently can't filter chrome extension requests, as this prevents the rendering of PDFs.
if (result == FilterResult.Block && !url.StartsWith("chrome-extension://"))
{
block = true;
logger.Info($"Blocked main request{(windowSettings.UrlPolicy.CanLog() ? $" for '{request.Url}'" : "")} ({request.ResourceType}, {request.TransitionType}).");
logger.Info($"Blocked main request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} ({request.ResourceType}, {request.TransitionType}).");
}
}
if (settings.Filter.ProcessContentRequests && request.ResourceType != ResourceType.MainFrame)
if (settings.Filter.ProcessContentRequests && request.ResourceType != ResourceType.MainFrame && isValidUrl)
{
var result = filter.Process(new Request { Url = request.Url });
var result = filter.Process(new Request { Url = url });
if (result == FilterResult.Block)
{
block = true;
logger.Info($"Blocked content request{(windowSettings.UrlPolicy.CanLog() ? $" for '{request.Url}'" : "")} ({request.ResourceType}, {request.TransitionType}).");
logger.Info($"Blocked content request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} ({request.ResourceType}, {request.TransitionType}).");
}
}
if (!isValidUrl)
{
logger.Warn($"Filter could not process request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} ({request.ResourceType}, {request.TransitionType})!");
}
return block;
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* Copyright (c) 2024 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -9,19 +9,21 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Mime;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using CefSharp;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SafeExamBrowser.Browser.Content;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Pages;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings;
using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.Settings.Browser.Filter;
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
@ -31,36 +33,39 @@ namespace SafeExamBrowser.Browser.Handlers
{
internal class ResourceHandler : CefSharp.Handler.ResourceRequestHandler
{
private SHA256Managed algorithm;
private AppConfig appConfig;
private string browserExamKey;
private IResourceHandler contentHandler;
private IRequestFilter filter;
private HtmlLoader htmlLoader;
private ILogger logger;
private IResourceHandler pageHandler;
private BrowserSettings settings;
private WindowSettings windowSettings;
private IText text;
private readonly AppConfig appConfig;
private readonly ContentLoader contentLoader;
private readonly IRequestFilter filter;
private readonly IKeyGenerator keyGenerator;
private readonly ILogger logger;
private readonly SessionMode sessionMode;
private readonly BrowserSettings settings;
private readonly WindowSettings windowSettings;
internal event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
private IResourceHandler contentHandler;
private IResourceHandler pageHandler;
private string userIdentifier;
internal event UserIdentifierDetectedEventHandler UserIdentifierDetected;
internal ResourceHandler(
AppConfig appConfig,
IRequestFilter filter,
IKeyGenerator keyGenerator,
ILogger logger,
SessionMode sessionMode,
BrowserSettings settings,
WindowSettings windowSettings,
IText text)
{
this.appConfig = appConfig;
this.algorithm = new SHA256Managed();
this.filter = filter;
this.htmlLoader = new HtmlLoader(text);
this.contentLoader = new ContentLoader(text);
this.keyGenerator = keyGenerator;
this.logger = logger;
this.sessionMode = sessionMode;
this.settings = settings;
this.windowSettings = windowSettings;
this.text = text;
}
protected override IResourceHandler GetResourceHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request)
@ -93,21 +98,27 @@ namespace SafeExamBrowser.Browser.Handlers
protected override void OnResourceRedirect(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, ref string newUrl)
{
SearchSessionIdentifiers(response);
if (sessionMode == SessionMode.Server)
{
SearchUserIdentifier(request, response);
}
base.OnResourceRedirect(chromiumWebBrowser, browser, frame, request, response, ref newUrl);
}
protected override bool OnResourceResponse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response)
{
if (RedirectToDisablePdfToolbar(request, response, out var url))
if (RedirectToDisablePdfReaderToolbar(request, response, out var url))
{
webBrowser.Load(url);
frame?.LoadUrl(url);
return true;
}
SearchSessionIdentifiers(response);
if (sessionMode == SessionMode.Server)
{
SearchUserIdentifier(request, response);
}
return base.OnResourceResponse(webBrowser, browser, frame, request, response);
}
@ -117,25 +128,18 @@ namespace SafeExamBrowser.Browser.Handlers
Uri.TryCreate(webBrowser.Address, UriKind.Absolute, out var pageUrl);
Uri.TryCreate(request.Url, UriKind.Absolute, out var requestUrl);
if (pageUrl?.Host?.Equals(requestUrl?.Host) == true)
if (request.ResourceType == ResourceType.MainFrame || pageUrl?.Host?.Equals(requestUrl?.Host) == true)
{
var headers = new NameValueCollection(request.Headers);
var urlWithoutFragment = request.Url.Split('#')[0];
if (settings.SendConfigurationKey)
{
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + settings.ConfigurationKey));
var key = BitConverter.ToString(hash).ToLower().Replace("-", string.Empty);
headers["X-SafeExamBrowser-ConfigKeyHash"] = key;
headers["X-SafeExamBrowser-ConfigKeyHash"] = keyGenerator.CalculateConfigurationKeyHash(settings.ConfigurationKey, request.Url);
}
if (settings.SendExamKey)
if (settings.SendBrowserExamKey)
{
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + (browserExamKey ?? ComputeBrowserExamKey())));
var key = BitConverter.ToString(hash).ToLower().Replace("-", string.Empty);
headers["X-SafeExamBrowser-RequestHash"] = key;
headers["X-SafeExamBrowser-RequestHash"] = keyGenerator.CalculateBrowserExamKeyHash(settings.ConfigurationKey, settings.BrowserExamKeySalt, request.Url);
}
request.Headers = headers;
@ -145,56 +149,42 @@ namespace SafeExamBrowser.Browser.Handlers
private bool Block(IRequest request)
{
var block = false;
var url = WebUtility.UrlDecode(request.Url);
var isValidUri = Uri.TryCreate(url, UriKind.Absolute, out _);
if (settings.Filter.ProcessContentRequests)
if (settings.Filter.ProcessContentRequests && isValidUri)
{
var result = filter.Process(new Request { Url = request.Url });
var result = filter.Process(new Request { Url = url });
if (result == FilterResult.Block)
{
block = true;
logger.Info($"Blocked content request{(windowSettings.UrlPolicy.CanLog() ? $" for '{request.Url}'" : "")} ({request.ResourceType}, {request.TransitionType}).");
logger.Info($"Blocked content request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} ({request.ResourceType}, {request.TransitionType}).");
}
}
else if (!isValidUri)
{
logger.Warn($"Filter could not process request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} ({request.ResourceType}, {request.TransitionType})!");
}
return block;
}
private string ComputeBrowserExamKey()
{
var salt = settings.ExamKeySalt;
if (salt == default(byte[]))
{
salt = new byte[0];
logger.Warn("The current configuration does not contain a salt value for the browser exam key!");
}
using (var algorithm = new HMACSHA256(salt))
{
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(appConfig.CodeSignatureHash + appConfig.ProgramBuildVersion + settings.ConfigurationKey));
var key = BitConverter.ToString(hash).ToLower().Replace("-", string.Empty);
browserExamKey = key;
return browserExamKey;
}
}
private bool IsMailtoUrl(string url)
{
return url.StartsWith(Uri.UriSchemeMailto);
}
private bool RedirectToDisablePdfToolbar(IRequest request, IResponse response, out string url)
private bool RedirectToDisablePdfReaderToolbar(IRequest request, IResponse response, out string url)
{
const string DISABLE_PDF_TOOLBAR = "#toolbar=0";
const string DISABLE_PDF_READER_TOOLBAR = "#toolbar=0";
var isPdf = response.Headers["Content-Type"] == MediaTypeNames.Application.Pdf;
var isMainFrame = request.ResourceType == ResourceType.MainFrame;
var hasFragment = request.Url.Contains(DISABLE_PDF_TOOLBAR);
var hasFragment = request.Url.Contains(DISABLE_PDF_READER_TOOLBAR);
var redirect = settings.AllowPdfReader && !settings.AllowPdfReaderToolbar && isPdf && isMainFrame && !hasFragment;
url = request.Url + DISABLE_PDF_TOOLBAR;
url = request.Url + DISABLE_PDF_READER_TOOLBAR;
if (redirect)
{
@ -225,12 +215,12 @@ namespace SafeExamBrowser.Browser.Handlers
{
if (contentHandler == default(IResourceHandler))
{
contentHandler = CefSharp.ResourceHandler.FromString(htmlLoader.LoadBlockedContent());
contentHandler = CefSharp.ResourceHandler.FromString(contentLoader.LoadBlockedContent());
}
if (pageHandler == default(IResourceHandler))
{
pageHandler = CefSharp.ResourceHandler.FromString(htmlLoader.LoadBlockedPage());
pageHandler = CefSharp.ResourceHandler.FromString(contentLoader.LoadBlockedPage());
}
switch (resourceType)
@ -243,15 +233,46 @@ namespace SafeExamBrowser.Browser.Handlers
}
}
private void SearchSessionIdentifiers(IResponse response)
private void SearchUserIdentifier(IRequest request, IResponse response)
{
SearchEdxIdentifier(response);
SearchMoodleIdentifier(response);
var success = TrySearchGenericUserIdentifier(response);
if (!success)
{
success = TrySearchEdxUserIdentifier(response);
}
if (!success)
{
TrySearchMoodleUserIdentifier(request, response);
}
}
private void SearchEdxIdentifier(IResponse response)
private bool TrySearchGenericUserIdentifier(IResponse response)
{
var ids = response.Headers.GetValues("X-LMS-USER-ID");
var success = false;
if (ids != default(string[]))
{
var userId = ids.FirstOrDefault();
if (userId != default && userIdentifier != userId)
{
userIdentifier = userId;
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
logger.Info("Generic LMS user identifier detected.");
success = true;
}
}
return success;
}
private bool TrySearchEdxUserIdentifier(IResponse response)
{
var cookies = response.Headers.GetValues("Set-Cookie");
var success = false;
if (cookies != default(string[]))
{
@ -259,7 +280,7 @@ namespace SafeExamBrowser.Browser.Handlers
{
var userInfo = cookies.FirstOrDefault(c => c.Contains("edx-user-info"));
if (userInfo != default(string))
if (userInfo != default)
{
var start = userInfo.IndexOf("=") + 1;
var end = userInfo.IndexOf("; expires");
@ -268,17 +289,42 @@ namespace SafeExamBrowser.Browser.Handlers
var json = JsonConvert.DeserializeObject(sanitized) as JObject;
var userName = json["username"].Value<string>();
Task.Run(() => SessionIdentifierDetected?.Invoke(userName));
if (userIdentifier != userName)
{
userIdentifier = userName;
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
logger.Info("EdX user identifier detected.");
success = true;
}
}
}
catch (Exception e)
{
logger.Error("Failed to parse edX session identifier!", e);
logger.Error("Failed to parse edX user identifier!", e);
}
}
return success;
}
private void SearchMoodleIdentifier(IResponse response)
private bool TrySearchMoodleUserIdentifier(IRequest request, IResponse response)
{
var success = TrySearchMoodleUserIdentifierByLocation(response);
if (!success)
{
success = TrySearchMoodleUserIdentifierByRequest(MoodleRequestType.Plugin, request, response);
}
if (!success)
{
success = TrySearchMoodleUserIdentifierByRequest(MoodleRequestType.Theme, request, response);
}
return success;
}
private bool TrySearchMoodleUserIdentifierByLocation(IResponse response)
{
var locations = response.Headers.GetValues("Location");
@ -288,18 +334,118 @@ namespace SafeExamBrowser.Browser.Handlers
{
var location = locations.FirstOrDefault(l => l.Contains("/login/index.php?testsession"));
if (location != default(string))
if (location != default)
{
var userId = location.Substring(location.IndexOf("=") + 1);
Task.Run(() => SessionIdentifierDetected?.Invoke(userId));
if (userIdentifier != userId)
{
userIdentifier = userId;
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
logger.Info("Moodle user identifier detected by location.");
}
return true;
}
}
catch (Exception e)
{
logger.Error("Failed to parse Moodle session identifier!", e);
logger.Error("Failed to parse Moodle user identifier by location!", e);
}
}
return false;
}
private bool TrySearchMoodleUserIdentifierByRequest(MoodleRequestType type, IRequest request, IResponse response)
{
var cookies = response.Headers.GetValues("Set-Cookie");
var success = false;
if (cookies != default(string[]))
{
var session = cookies.FirstOrDefault(c => c.Contains("MoodleSession"));
if (session != default)
{
var userId = ExecuteMoodleUserIdentifierRequest(request.Url, session, type);
if (int.TryParse(userId, out var id) && id > 0 && userIdentifier != userId)
{
userIdentifier = userId;
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
logger.Info($"Moodle user identifier detected by request ({type}).");
success = true;
}
}
}
return success;
}
private string ExecuteMoodleUserIdentifierRequest(string requestUrl, string session, MoodleRequestType type)
{
var userId = default(string);
try
{
Task.Run(async () =>
{
try
{
var endpointUrl = default(string);
var start = session.IndexOf("=") + 1;
var end = session.IndexOf(";");
var name = session.Substring(0, start - 1);
var value = session.Substring(start, end - start);
var uri = new Uri(requestUrl);
if (type == MoodleRequestType.Plugin)
{
endpointUrl = $"{uri.Scheme}{Uri.SchemeDelimiter}{uri.Host}/mod/quiz/accessrule/sebserver/classes/external/user.php";
}
else
{
endpointUrl = $"{uri.Scheme}{Uri.SchemeDelimiter}{uri.Host}/theme/boost_ethz/sebuser.php";
}
var message = new HttpRequestMessage(HttpMethod.Get, endpointUrl);
using (var handler = new HttpClientHandler { UseCookies = false })
using (var client = new HttpClient(handler))
{
message.Headers.Add("Cookie", $"{name}={value}");
var result = await client.SendAsync(message);
if (result.IsSuccessStatusCode)
{
userId = await result.Content.ReadAsStringAsync();
}
else if (result.StatusCode != HttpStatusCode.NotFound)
{
logger.Error($"Failed to retrieve Moodle user identifier by request ({type})! Response: {(int) result.StatusCode} {result.ReasonPhrase}");
}
}
}
catch (Exception e)
{
logger.Error($"Failed to parse Moodle user identifier by request ({type})!", e);
}
}).GetAwaiter().GetResult();
}
catch (Exception e)
{
logger.Error($"Failed to execute Moodle user identifier request ({type})!", e);
}
return userId;
}
private enum MoodleRequestType
{
Plugin,
Theme
}
}
}

Some files were not shown because too many files have changed in this diff Show more