Compare commits

..

945 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
Damian Büchel
cb84fbd689 Removed BETA marker for version 3.1.1. 2020-12-17 16:12:04 +01:00
Damian Büchel
08cb00babd #47: Changed language code for chinese translation. 2020-12-16 11:26:38 +01:00
Damian Büchel
2a0b038bf3 Updated readme with new VC++ redistributable dependency. 2020-12-14 11:28:36 +01:00
Damian Büchel
4bccf4610b #28 : Ensured nodrives mechanism in file system dialog doesn't crash if registry value doesn't exist. 2020-12-11 09:58:31 +01:00
Damian Büchel
9b8379bdae SEBWIN-445: Ensured battery and WLAN data is only sent to server when values change. 2020-12-10 17:42:50 +01:00
Damian Büchel
86242911a2 Fixed verification of event handler subscriptions in unit tests. 2020-12-04 16:17:13 +01:00
Damian Büchel
b1997b5b56 Updated dependencies and browser to latest version (86.0.4240.198). 2020-12-04 15:37:34 +01:00
Damian Büchel
ba523d4381 SEBWIN-447: Implemented missing remote session check during session initialization. 2020-12-04 15:21:51 +01:00
Damian Büchel
d2407afebf SEBWIN-446: Ensured missing machine info doesn't result in fatal error during startup. 2020-12-04 13:44:40 +01:00
Damian Büchel
b8393f2ae5 SEBWIN-443: Updated setup bundle to install Visual C++ 2015-2019 Redistributable instead of Visual C++ 2015 Redistributable. 2020-12-04 12:38:52 +01:00
Damian Büchel
7ccc774277 Added French translation. 2020-12-04 11:00:48 +01:00
Damian Büchel
ec7e3d2e18 Corrected typo. 2020-12-03 19:48:00 +01:00
Damian Büchel
79a221cf21 SEBWIN-445: Implemented reporting of power supply and wireless adapter status to server. 2020-12-03 19:47:17 +01:00
Damian Büchel
4d05ef8cad SEBWIN-444: Fixed issue with starting a server exam when reconfiguring. 2020-12-03 18:19:18 +01:00
Damian Büchel
944461f902 Added Chinese translation. 2020-12-03 18:01:35 +01:00
Damian Büchel
a3292a7977 SEBWIN-444: Ensured server connectivity is maintained when reconfiguring and fixed detection of Moodle sessions. 2020-12-02 17:43:02 +01:00
Damian Büchel
786193aff4 SEBWIN-444: Changed server operation implementation to not re-initialize connection during reconfiguration. 2020-12-01 18:25:53 +01:00
Damian Büchel
15be4cbaf7 SEBWIN-442: Implemented mechanism to automatically select server exam via configuration. 2020-11-30 18:30:29 +01:00
Damian Büchel
1cf9d53121 SEBWIN-441: Ensured custom headers are only appended to same-domain requests. 2020-11-27 15:14:33 +01:00
Damian Büchel
907b251232 Reverted version to 3.1.1. 2020-11-24 10:57:37 +01:00
Damian Büchel
6fad725744 Updated version to 3.2.0. 2020-11-12 13:57:03 +01:00
Damian Büchel
c243f4fe4e Removed beta marker for version 3.1.0. 2020-11-09 15:45:33 +01:00
Damian Büchel
7d55552889 SEBWIN-427: Ensured seb(s) URLs are correctly handled for secure session reconfiguration. 2020-10-27 19:50:03 +01:00
Damian Büchel
3698d7dabb Updated browser engine and dependencies to latest version (CEF 85.3.13). 2020-10-26 14:08:51 +01:00
Damian Büchel
3ef3bccd7e SEBWIN-426: Ensured that lock screen is not activated when lock or switch session is allowed. 2020-10-23 09:31:23 +02:00
Damian Büchel
f6a3cb485b SEBWIN-425: Ensured columns are not sortable for configuration of third-party applications. 2020-10-21 16:28:45 +02:00
Damian Büchel
5d7630432e SEBWIN-425: Added new prohibited processes. 2020-10-20 19:13:50 +02:00
Damian Büchel
d1f0e012d5 SEBWIN-414: Implemented home functionality for browser. 2020-10-05 23:37:23 +02:00
Damian Büchel
ae7dcc0b0b Code cleanup. 2020-09-29 14:37:54 +02:00
Damian Büchel
bd5eab03a1 SEBWIN-424: Increased timeout for service communication host. 2020-09-29 14:14:26 +02:00
Damian Büchel
68decb740c SEBWIN-414: Implemented new feature to control displaying and logging of URLs in browser. 2020-09-29 14:01:17 +02:00
Damian Büchel
3a23cec2c2 SEBWIN-414: Ensured query string parameter is correctly appended to start URL. 2020-09-24 17:01:23 +02:00
Damian Büchel
50e943e8a8 SEBWIN-414: Implemented query string parameter feature. 2020-09-24 12:55:20 +02:00
Damian Büchel
12c342cc7d Merge branch 'master' of https://github.com/SafeExamBrowser/seb-win-refactoring 2020-09-22 16:55:36 +02:00
Damian Büchel
e5f2c4f33f SEBWIN-423: Ensured that AltGr does not trigger termination. 2020-09-22 16:55:31 +02:00
Damian Büchel
cca6c7526a
Merge pull request #29 from jp-weber/master
Update FileSystemDialog.xaml.cs
2020-09-21 19:36:49 +02:00
Damian Büchel
6d478660b5 SEBWIN-414: Implemented feature to reset browser via quit URL. 2020-09-21 19:22:15 +02:00
Jan Philipp Weber
8908efc93e - mobile UI adapted 2020-09-18 19:22:24 +02:00
Damian Büchel
eea29761be SEBWIN-414: Updated implementation of reconfiguration mechanism according to changed requirements. 2020-09-15 17:16:48 +02:00
Damian Büchel
252e807c44 SEBWIN-414: Implemented new mechanism to allow or deny reconfiguration. 2020-09-10 12:35:58 +02:00
Damian Büchel
8a385a55d0 Ensured missing content type does not crash network resource loader. 2020-09-08 15:32:45 +02:00
Damian Büchel
843744c70f SEBWIN-421: Added info about required disk space for .NET framework. 2020-09-07 12:13:39 +02:00
Jan Philipp Weber
75e0737acd
Fixed wrong replacements 2020-09-05 17:41:18 +02:00
Jan Philipp Weber
c970ce6873
Update FileSystemDialog.xaml.cs 2020-09-04 12:52:15 +02:00
Jan Philipp Weber
2e31d8c983
Update FileSystemDialog.xaml.cs 2020-09-04 12:04:17 +02:00
Damian Büchel
f2be4495c4 SEBWIN-419: Attempt to fix desktop reinitalization issue when closing / opening laptop. 2020-09-02 16:21:52 +02:00
Damian Büchel
8349aa5e38 SEBWIN-421: Added info about .NET installer in bundle failure message. 2020-08-28 17:15:23 +02:00
Damian Büchel
68360a603d SEBWIN-420: Ensured the temporary directory is always created for every new session. 2020-08-27 20:10:15 +02:00
Damian Büchel
867bcfe6a7 Updated browser engine to version 84.4.10. 2020-08-26 19:09:44 +02:00
Damian Büchel
4345ac22cd SEBWIN-413: Fixed clearing of results for text search. 2020-08-26 13:03:17 +02:00
Damian Büchel
86594b23ca Added Italian text translation. 2020-08-21 19:14:39 +02:00
Damian Büchel
af71bbd72e SEBWIN-413: Made find functionality configurable. 2020-08-11 17:52:51 +02:00
Damian Büchel
7a91d34ca8 SEBWIN-413: Implemented basic find toolbar for browser. 2020-08-10 21:42:51 +02:00
Damian Büchel
a7e3657c6a Added Newtonsoft.Json to used libraries. 2020-08-06 14:39:14 +02:00
Damian Büchel
e0d0fb8379 Removed unused load error page. 2020-08-06 14:30:37 +02:00
Damian Büchel
a82f2d5780 SEBWIN-416: Ensured third-party applications with custom URI schemes are correctly opened. 2020-08-06 14:26:40 +02:00
Damian Büchel
63e8f9b45e SEBWIN-415: Fixed client hang after startup failure. 2020-08-06 14:06:29 +02:00
Damian Büchel
9817da759c SEBWIN-405: Fixed timestamp format for server communication. 2020-08-06 13:21:51 +02:00
Damian Büchel
2ade9e15b4 SEBWIN-407: Implemented access restriction for file system dialog. 2020-08-05 22:55:38 +02:00
Damian Büchel
8d94750078 SEBWIN-405: Fixed and improved LMS session detection. 2020-08-03 14:41:25 +02:00
Damian Büchel
682c2a2ce5 SEBWIN-405: Fixed issue with server disconnection. 2020-08-03 11:51:21 +02:00
Damian Büchel
43bdfbb411 SEBWIN-405: Fixed issue with parsing of server instruction. 2020-08-01 19:31:27 +02:00
Damian Büchel
09fbc6579a SEBWIN-405: Changed implementation for sending of log events and implemented server quit event. 2020-08-01 17:55:18 +02:00
Damian Büchel
6997d3a5f5 SEBWIN-405: Implemented mobile versions for exam selection and server failure dialog. 2020-07-31 22:17:10 +02:00
Damian Büchel
4af0cc0d48 SEBWIN-405: Implemented mechanism to retrieve server failure action via client. 2020-07-31 20:35:18 +02:00
Damian Büchel
4d59ee399d SEBWIN-405: Implemented mechanism to retrieve exam selection via client. 2020-07-31 19:57:08 +02:00
Damian Büchel
7ac34b3473 SEBWIN-405: Implemented draft of mechanism to send pings and logs. 2020-07-31 14:37:12 +02:00
Damian Büchel
facc8c9442 SEBWIN-405: Implemented detection of Moodle session identifier and server disconnection. 2020-07-31 13:24:42 +02:00
Damian Büchel
22f6e8b664 SEBWIN-405: Implemented scaffolding for detection of session identifier. 2020-07-29 23:39:05 +02:00
Damian Büchel
bc06a0c985 SEBWIN-405: Prepared infrastructure in client for server functionality. 2020-07-28 19:56:25 +02:00
Damian Büchel
ef13cfe9c5 SEBWIN-405: Implemented loading of server exam configuration. 2020-07-27 15:58:30 +02:00
Damian Büchel
7915d4dff9 SEBWIN-405: Implemented server failure dialog. 2020-07-24 18:22:22 +02:00
Damian Büchel
60e8457033 Attempt to fix command-line overflow on release build server. 2020-07-22 18:51:56 +02:00
Damian Büchel
276f84c0ed Attempt to fix CI build error. 2020-07-22 18:30:08 +02:00
Damian Büchel
c2cd3a742f SEBWIN-405: Implemented basic server binding up to exam selection. 2020-07-22 18:11:51 +02:00
Damian Büchel
0edca494b3 SEBWIN-405: Implemented scaffolding for SEB server operation. 2020-07-13 22:57:19 +02:00
Damian Büchel
92ab58f988 Added BETA marker for version 3.1.0. 2020-07-13 20:09:18 +02:00
Damian Büchel
753ada8751
Merge pull request #17 from diegoara96/master
Fix issue #14
2020-07-13 19:24:52 +02:00
diegoara96
11bf69e0e2 Refactored mac detection 2020-07-13 18:55:42 +02:00
diegoara96
a574e2f241 Changed Mac detection 2020-07-10 22:22:37 +02:00
Damian Büchel
b5a72b122c
Delete bug_report.md 2020-07-07 13:10:06 +02:00
Damian Büchel
261a331634 SEBWIN-405: Implemented server settings and data mapping. 2020-07-01 13:39:17 +02:00
Damian Büchel
0911e23714 SEBWIN-405: Added assemblies for SEB server. 2020-06-30 17:22:36 +02:00
Damian Büchel
9599102b9e SEBWIN-414: Implemented lock screen for user session switch. 2020-06-29 19:29:48 +02:00
Damian Büchel
44a7275f40 Created issue templates. 2020-06-26 11:31:49 +02:00
Damian Büchel
7bb21c35a9 Updated version to 3.1.0 2020-06-25 18:13:53 +02:00
Damian Büchel
48d0dc4db6 SEBWIN-411: Fixed audio / video playback not stopping when closing additional browser window. 2020-06-23 16:53:43 +02:00
Damian Büchel
c8b6aad877 SEBWIN-410: Implemented new default behavior to ignore the SEB service. 2020-06-23 13:53:10 +02:00
Damian Büchel
1e686c2909 Updated version to 3.0.1 2020-06-22 16:45:19 +02:00
Damian Büchel
0f1d57744e Updated browser engine to version 81.3.100 and updated dependencies for entire solution. 2020-06-22 12:03:29 +02:00
Damian Büchel
eb8ac889a4 SEBWIN-408: Made favicon loading asynchronous. 2020-06-22 11:31:42 +02:00
Damian Büchel
bcb8abdeaa SEBWIN-409: Fixed handling of spaces in FileResourceSaver. 2020-06-22 10:55:26 +02:00
Damian Büchel
5cefbc5a7b
Merge pull request #10 from diegoara96/master
improvement in the vendor's list
2020-06-04 20:21:01 +02:00
diegoara96
23b1433926 improvement in the vendor's list 2020-06-04 16:14:50 +02:00
Damian Büchel
84c8df465e Removed "BETA" from informational version. 2020-05-27 16:31:48 +02:00
Damian Büchel
7b8c69c1ba SEBWIN-401: Implemented fix for VMware issue: The registry values will now only be set if the active configuration explicity says so. 2020-05-20 12:03:54 +02:00
Damian Büchel
87882c3aa5 Fixed issue with missing entry assembly in unit tests. 2020-05-19 15:17:12 +02:00
Damian Büchel
60731e01a8 Fixed program title by using entry assembly instead of executing assembly for app config data. 2020-05-19 14:32:39 +02:00
Damian Büchel
9ec8130a8a SEBWIN-395: Fixed issue with request filter blocking load error page. 2020-05-15 16:13:15 +02:00
Damian Büchel
03cf219c9b SEBWIN-398: Changed temporary setup file name in release server build script to avoid concurrency issues with Chrominimum. 2020-05-13 18:33:20 +02:00
Damian Büchel
2c6de99388 SEBWIN-367, SEBWIN-390: Forgot to fix configuration key for network selector in legacy configuration tool. 2020-05-13 14:19:19 +02:00
Damian Büchel
392a730ea5 SEBWIN-368: Final attempt to fix issues with touch activator for action center. 2020-05-12 19:24:41 +02:00
Damian Büchel
225ffe47a7 SEBWIN-368: Attempt to improve touch activator of action center for high-definition touch screens. 2020-05-12 13:19:06 +02:00
Damian Büchel
316e2ae8b8 SEBWIN-368: Increased timeout for touch activation of action center. 2020-05-12 12:42:55 +02:00
Damian Büchel
bf7fdf3f5f
Merge pull request #6 from diegoara96/master
Virtualized MAC and PCI vendor detection
2020-05-07 16:28:50 +02:00
diegoara96
83c82f799f Minor fixes and Mac > 2 check 2020-05-07 13:21:57 +02:00
diegoara96
1de5848edb Fixed name Vendor #6 and new Property added 2020-05-06 21:45:04 +02:00
diegoara96
bcdfa36e0d Fixed mac detection #6 and new Property added 2020-05-06 18:44:08 +02:00
Diego Araujo Novoa
09282a9e95
Update VirtualMachineDetector.cs 2020-05-05 15:13:52 +02:00
Diego Araujo Novoa
5bd6e0091d
Added a missing namespace 2020-05-05 15:09:48 +02:00
Diego Araujo Novoa
aa65b8b2d1
Virtualized MAC and PCI vendor detection 2020-05-05 14:44:22 +02:00
Damian Büchel
16f2e22f2a SEBWIN-305: Fixed data mapping for browser window widths and heights. 2020-05-04 13:47:32 +02:00
Damian Büchel
63d34825f7 SEBWIN-397: Implemented mechanism to automatically show the browser window toolbar if reloading is enabled. 2020-05-04 12:56:08 +02:00
Damian Büchel
b5876eb61a SEBWIN-396: Implemented configuration value "allowQuit" to control the termination of SEB by user action. 2020-05-04 12:37:54 +02:00
Damian Büchel
631fb583d7 SEBWIN-392: Forgot to actually use the arguments of a whitelisted application. 2020-04-29 18:15:50 +02:00
Damian Büchel
bdc8dad413 SEBWIN-389: Changed label of security setting for print screen / screen capture. 2020-04-28 19:07:00 +02:00
Damian Büchel
a6aad36a05
Merge pull request #5 from diegoara96/master
qemu detection added
2020-04-28 19:00:26 +02:00
Diego Araujo Novoa
d206f6cf98
qemu detection added 2020-04-28 13:28:59 +02:00
Damian Büchel
ec95d3fd9b SEBWIN-390: Fixed spelling mistake in configuration key for service component. 2020-04-17 10:39:58 +02:00
Damian Büchel
1763394036 SEBWIN-380: Extended tests for configuration data processor. 2020-04-08 16:06:59 +02:00
Damian Büchel
ad24146d9f SEBWIN-380: Implemented unit tests for browser resource handler. 2020-04-06 14:13:13 +02:00
Damian Büchel
2572fc10d7 SEBWIN-380: Implemented unit tests for browser request handler. 2020-04-06 11:06:42 +02:00
Damian Büchel
d3ce3ee9e1 SEBWIN-380: Started implementing unit tests for browser component handlers. 2020-04-03 15:22:47 +02:00
Damian Büchel
5eff32a7bc SEBWIN-380: Added browser engine reference to browser unit tests in order to be able to unit test important components. 2020-04-02 17:56:48 +02:00
Damian Büchel
735e0b6dca SEBWIN-384: Ensured the action center is automatically hidden during shell initialization. 2020-04-01 13:49:32 +02:00
Damian Büchel
3840fb4e84 SEBWIN-362: Implemented fallback mechanism in case HEAD requests do not work for certain network resources. 2020-03-30 11:49:47 +02:00
Damian Büchel
246bc81d79 SEBWIN-383: Corrected redirect mechanism for PDF toolbar to only redirect main frame requests and added missing new option to configuration tool. 2020-03-27 13:18:24 +01:00
Damian Büchel
7bf4e0308f SEBWIN-351: Implemented utter hack in attempt to make legacy configuration tool calculate the same configuration key in versions 2.x and 3.x. 2020-03-25 12:04:43 +01:00
Damian Büchel
f1d8e14719 SEBWIN-382: Fixed naming of configuration key in configuration tool. 2020-03-23 12:04:57 +01:00
Damian Büchel
9e8c5056cf SEBWIN-362: Ensured load error page is not rendered when a request is aborted. 2020-03-18 10:24:22 +01:00
Damian Büchel
34cf9b9cf4 SEBWIN-362: Corrected format of new icons. 2020-03-17 11:22:30 +01:00
Damian Büchel
8e075264a4 SEBWIN-362: Ensured all UI element implementations are only accessible via a façade. 2020-03-17 11:07:40 +01:00
Damian Büchel
dc56a81760 SEBWIN-362: Cleaned up mobile UI implementations. 2020-03-17 10:37:08 +01:00
Damian Büchel
9c889ac82d SEBWIN-362: Cleaned up desktop UI implementations. 2020-03-16 18:29:06 +01:00
Damian Büchel
6032cdf688 SEBWIN-362: Fixed new icons for application executables. 2020-03-16 16:05:10 +01:00
Damian Büchel
f60297684d SEBWIN-362: Updated icons for configuration tool and reset utility. 2020-03-16 15:25:49 +01:00
Damian Büchel
63152a0335 SEBWIN-362: Added retry option for load error page. 2020-03-16 14:20:53 +01:00
Damian Büchel
6163c98e08 SEBWIN-362: Removed unused contracts (and assemblies) for client, runtime and service controllers. 2020-03-16 13:38:25 +01:00
Damian Büchel
f0504fa6d2 SEBWIN-362: Implemented basic load error page for browser. 2020-03-13 15:56:32 +01:00
Damian Büchel
049cf8fe19 SEBWIN-362: Added custom images for setup and setup bundle. 2020-03-13 10:28:04 +01:00
Damian Büchel
c8bebbfa82 SEBWIN-366: Fixed bug where wireless network was displayed as disconnected even though it actually was connected. 2020-03-13 07:56:28 +01:00
Damian Büchel
4b9ec6e2d3 Added "BETA" to informational version. 2020-03-12 12:11:58 +01:00
Damian Büchel
fa9dd4ec5b SEBWIN-368: Made action center easier to activate via touch gesture. 2020-03-12 10:29:50 +01:00
Damian Büchel
54b5be830e SEBWIN-379: Fixed request filter expressions containing trailing slashes after host. 2020-03-11 11:33:52 +01:00
Damian Büchel
e5659632b9 SEBWIN-356: Changed I18n implementation to automatically load text data for current system language. 2020-03-09 17:35:48 +01:00
Damian Büchel
b465d498d0 SEBWIN-378: Implemented basic support for default spell checking. 2020-03-05 09:38:26 +01:00
Damian Büchel
6efd075ed5 SEBWIN-377: Ensured password dialog has focus when loaded. 2020-03-04 15:54:27 +01:00
Damian Büchel
7ee2c1031f Added new configuration option for browser developer console to configuration tool. 2020-03-04 10:34:10 +01:00
Damian Büchel
24c5d91fe4 SEBWIN-376: Fixed error with browser request filtering and improved logging if request is blocked. Also fixed error in configuration value mapping for content request filter. 2020-03-04 10:08:34 +01:00
Damian Büchel
50e671c40c SEBWIN-374: Implemented sanitation mechanism for browser engine data in legacy configuration files. 2020-03-03 15:38:48 +01:00
Damian Büchel
7d8720d6a2 SEBWIN-369: Ensured quit link ignores trailing slash. 2020-03-02 10:46:42 +01:00
Damian Büchel
09b7da5eae SEBWIN-373: Turns out that it isn't necessary to terminate the Windows shell for kiosk mode Create New Desktop, at least with version 1909. Version 1803 did not allow SEB to set its own working area, now it appears to work even while the Windows shell is running. Amazing. 2020-03-01 11:13:41 +01:00
Damian Büchel
1dedefaea4 Marked private clipboard as macOS feature for 3.0.0. 2020-03-01 10:59:02 +01:00
Damian Büchel
5f01973b57 SEBWIN-373: Fixed system freeze caused by kiosk mode Create New Desktop due to it freezing the Windows shell. Thus, the Windows shell will henceforth be terminated for both kiosk modes. Furthermore ensured that processes started by SEB do not retain handles to the SEB directory. 2020-02-28 18:59:46 +01:00
Damian Büchel
ba4d4cec81 SEBWIN-367: Implemented mapping for service configuration values and added new options in configuration tool. 2020-02-28 15:00:17 +01:00
dbuechel
2b7ff4561a SEBWIN-361: Fixed concurrency issue when terminating external applications and ensured termination operation continues even with failure. 2020-02-26 08:49:16 +01:00
dbuechel
07bb78e637 SEBWIN-363, SEBWIN-357: Ensured session is retained when loading a configuration from a server which requires authentication and introduced new flag to determine whether a reconfiguration is allowed or not. Also fixed session persistence when using delete cookies settings. 2020-02-25 10:41:55 +01:00
dbuechel
1d9f5ffad7 SEBWIN-358: Implemented configuration and mechanism for browser cache deletion. 2020-02-21 15:11:35 +01:00
dbuechel
9d0c005b35 SEBWIN-309, SEBWIN-358: Fixed crash happening when a configuration does not contain a salt value for the browser exam key. 2020-02-21 11:58:08 +01:00
dbuechel
6865798fb5 SEBWIN-358: Fixed bug with disabling of taskbar and removed taskbar height configuration option. 2020-02-21 09:38:39 +01:00
dbuechel
c43df2e712 SEBWIN-358: Implemented user agent suffix. 2020-02-19 15:32:38 +01:00
dbuechel
6ad5d062db SEBWIN-309, SEBWIN-358: Corrected usage of salt value for browser exam key. 2020-02-19 15:21:34 +01:00
dbuechel
1a840ffac5 SEBWIN-358: Integrated browser exam key and configuration key computation in legacy configuration tool. 2020-02-19 14:30:39 +01:00
dbuechel
6e560e1a3e SEBWIN-358: Added new configuration options to configuration tool. 2020-02-19 11:53:29 +01:00
dbuechel
08530173c4 SEBWIN-358: Added file extensions to default prohibited processes. 2020-02-19 10:11:14 +01:00
dbuechel
93fbaa6001 SEBWIN-358: Removed configuration tab for Additional Resources. 2020-02-19 09:41:38 +01:00
dbuechel
28f5b88f25 SEBWIN-358: Fixed message box title & text for legacy configuration tool and disabled / marked configuration options not yet available in SEB 3.0. 2020-02-19 09:02:18 +01:00
dbuechel
38159dae3e Removed remarks regarding workaround for browser user agent. It appears that the user agent can't be changed on a per-request basis (at least for requests from service workers). 2020-02-17 15:44:22 +01:00
dbuechel
13aec073cb Corrected readme regarding prerequisites. 2020-02-17 15:12:42 +01:00
dbuechel
91765e2d55 SEBWIN-357: Implemented browser session configuration. 2020-02-17 12:10:04 +01:00
dbuechel
42e107d7c7 SEBWIN-360: Improved runtime performance by having only one splash screen. 2020-02-14 15:04:33 +01:00
dbuechel
19bf6df812 SEBWIN-360: Fixed concurrency issue when reconfiguring and improved client performance by having only one splash screen. 2020-02-14 14:43:08 +01:00
dbuechel
d5210e85b7 Updated browser engine to version 79.1.36 and updated dependencies for entire solution. 2020-02-14 09:51:52 +01:00
dbuechel
9f8920b410 SEBWIN-309: Corrected implementation of configuration and browser exam key. 2020-02-13 11:01:07 +01:00
dbuechel
ad023853d4 SEBWIN-359: Fixed freeze during startup or application start by explicitly disabling shell execution when starting a process, as this appears to be the root cause of the freeze on Windows 10 version 1903 and above. 2020-02-12 15:21:12 +01:00
dbuechel
7e3703dc16 Added missing default values for browser and configuration mode. 2020-02-11 10:34:23 +01:00
dbuechel
2cde60b1e7 Fixed issue with splash screen not being closed on client side if reconfiguration was aborted. 2020-02-10 16:47:50 +01:00
dbuechel
5ce5c78641 SEBWIN-309: Implemented draft of browser exam key. 2020-02-10 12:19:25 +01:00
dbuechel
92d22a8437 SEBWIN-309: Extended detection mechanism for configuration file downloads. 2020-02-06 11:10:22 +01:00
dbuechel
7df1fe5f03 SEBWIN-309: Corrected implementation of configuration key. 2020-02-06 09:56:32 +01:00
dbuechel
f89b0d8a2a SEBWIN-352: Extended unit tests for XML parser. 2020-01-30 16:07:24 +01:00
dbuechel
de00dbc13c SEBWIN-352: Implemented new configuration format, i.e. parsing of compressed XML data. Also removed BOM from XML unit test data file. 2020-01-30 15:20:05 +01:00
dbuechel
15f9acd917 Corrected message box title and text which accidentally got swapped. 2020-01-30 11:38:41 +01:00
dbuechel
1c2c328fc7 Disabled configuration options which are not supported by SEB 3.0.0. 2020-01-30 11:27:49 +01:00
dbuechel
603c268839 SEBWIN-302: Implemented configuration of internal PDF reader. 2020-01-30 11:15:28 +01:00
dbuechel
cf2a74f6ce SEBWIN-308: Ensured temporary configuration file is deleted after reconfiguration. 2020-01-29 10:07:28 +01:00
dbuechel
c3a2fb38ce Ensured naming consistency for parent window of file system dialog. 2020-01-24 11:07:52 +01:00
dbuechel
b003bf93b7 SEBWIN-308: Implemented mechanism to block uploads. 2020-01-24 10:19:11 +01:00
dbuechel
c1aa080f87 SEBWIN-308, SEBWIN-312: Replaced BCL folder dialog with custom implementation. 2020-01-22 16:08:57 +01:00
dbuechel
97f3fb4a02 SEBWIN-308: Implemented basic download overview for browser. 2020-01-22 15:16:11 +01:00
dbuechel
b9536c6a1b Unified license information in about windows. 2020-01-21 09:43:25 +01:00
dbuechel
0ec1a446f8 SEBWIN-308: Implemented file system dialog for mobile UI. 2020-01-21 08:48:03 +01:00
dbuechel
2fdde50d0e Updated license for KnownFolders library. 2020-01-20 17:10:15 +01:00
dbuechel
0083a87bce SEBWIN-308: Implemented basic file system dialog and download configuration for browser. 2020-01-20 16:13:08 +01:00
dbuechel
61f369a9a3 SEBWIN-307: Implemented configuration to enable / disable the browser application. 2020-01-10 10:25:51 +01:00
dbuechel
5e131289b0 SEBWIN-309: Implemented scaffolding for Configuration Key. 2020-01-10 08:54:10 +01:00
dbuechel
45e1b001e3 SEBWIN-302: Implemented configuration for browser window toolbar. 2020-01-08 09:55:23 +01:00
dbuechel
df61e79861 Updated year in copyright and license remarks. 2020-01-06 15:24:46 +01:00
dbuechel
18fb059ddc SEBWIN-316: Implemented rudimentary VM detection. 2020-01-06 15:11:57 +01:00
dbuechel
bc0170976c SEBWIN-316: Ensured Windows 7 & 8.1 support for computer system information. 2019-12-20 17:52:38 +01:00
dbuechel
bf69a64e15 SEBWIN-316: Added manufacturer, model and name to system info. 2019-12-20 17:06:28 +01:00
dbuechel
175a2e8cf7 SEBWIN-316: Finally grouped all security related settings. Implemented mapping for virtual machine policy. 2019-12-20 11:37:07 +01:00
dbuechel
955ae3545e Refactored configuration data mapping. 2019-12-20 10:03:47 +01:00
dbuechel
130dd45ff6 SEBWIN-310: Implemented quit URL. 2019-12-19 15:02:40 +01:00
dbuechel
4b415a5f45 SEBWIN-314: Corrected simplified rule implementation for alphanumeric expression. 2019-12-18 10:09:30 +01:00
dbuechel
42eccef565 Cleaned and restructured browser settings namespace. 2019-12-18 08:24:55 +01:00
dbuechel
5b3a2a3861 SEBWIN-315: Implemented basic proxy support. 2019-12-18 08:09:59 +01:00
dbuechel
8953313642 SEBWIN-355: Improved process factory to ensure process termination in between .NET and WMI data access is handled gracefully. 2019-12-16 11:49:59 +01:00
dbuechel
eb3a87016e SEBWIN-304: Implemented same host policies for browser popups. 2019-12-13 16:10:10 +01:00
dbuechel
1f4043619f SEBWIN-304: Implemented browser popup policy. 2019-12-12 15:41:05 +01:00
dbuechel
a54513259d SEBWIN-305: Default values for browser window size and position. 2019-12-11 12:24:05 +01:00
dbuechel
90b73ec8aa SEBWIN-305: Implemented size and position configuration for browser windows. 2019-12-11 11:09:24 +01:00
dbuechel
cf4e229fef SEBWIN-353: Fixed bug with service session not being terminated when startup is aborted. Removed ignore flag from service proxy, as it did not serve any real purpose. 2019-12-10 14:22:18 +01:00
dbuechel
69c09dcb35 SEBWIN-312: Extended unit tests for client controller. 2019-12-09 16:25:03 +01:00
dbuechel
58a0cd134e Removed "Base" from informational version. 2019-12-09 14:19:29 +01:00
dbuechel
dee3991616 SEBWIN-312: Implemented unit tests for application operation. 2019-12-09 14:01:11 +01:00
dbuechel
4894d48d6d SEBWIN-312: Extended unit tests for shell operation. 2019-12-09 10:41:52 +01:00
dbuechel
23c29d1ba4 SEBWIN-312: Extended unit tests for browser operation. 2019-12-09 09:53:08 +01:00
dbuechel
c3bbc87b71 Excluded unit tests from release build configuration. 2019-12-09 09:27:10 +01:00
dbuechel
6e7ddf1f8a SEBWIN-312: Implemented folder dialog for custom application path selection. 2019-12-06 17:42:46 +01:00
dbuechel
b1781ba1ed Fixed layout issues with taskbar popups. 2019-12-06 14:23:07 +01:00
dbuechel
a18a404ed6 SEBWIN-312: Ensured applications which are allowed to be running are not automatically terminated. 2019-12-06 10:47:44 +01:00
dbuechel
3626fbe74f SEBWIN-312: Made extension method out of WindowUtility. 2019-12-06 10:04:22 +01:00
dbuechel
7cbf9c39d3 SEBWIN-312: Implemented thumbnails of open windows for mobile taskview. 2019-12-05 12:35:59 +01:00
dbuechel
a93678d5d7 SEBWIN-312: Implemented thumbnails of open windows for desktop taskview. 2019-12-05 11:54:43 +01:00
dbuechel
018e596905 SEBWIN-312: Implemented icon change event for application windows and finally moved IconResource from core to application namespace. 2019-12-03 15:43:48 +01:00
dbuechel
d8a27e9298 SEBWIN-312: Ensured already known application instances are not initialized twice. 2019-12-02 17:00:55 +01:00
dbuechel
f1fc27e451 SEBWIN-312: Only show an application in the shell if the configuration says so. 2019-12-02 16:39:49 +01:00
dbuechel
1ffb796963 SEBWIN-312: Removed lazy loading of names for processes. 2019-12-02 16:11:44 +01:00
dbuechel
df13e96dcd SEBWIN-312: Implemented mechanism to detect start of whitelisted application instances. 2019-12-02 15:48:06 +01:00
dbuechel
f19f284d95 SEBWIN-312: Implemented basic window handling for external applications. Reverted wrong cleanup logic for native handles. 2019-11-29 14:59:54 +01:00
dbuechel
4503cbe778 SEBWIN-312: Removed IApplicationInstance from API as it is irrelevant to the shell (which cares only about applications and their windows). 2019-11-28 17:22:04 +01:00
dbuechel
fa9b84ba68 SEBWIN-314: Fixed issues with legacy URL filter implementation. 2019-11-22 15:28:05 +01:00
dbuechel
84be7afa97 Added option to keep log window topmost. 2019-11-22 15:21:14 +01:00
dbuechel
6ab7047639 SEBWIN-312: Corrected original name check for whitelisted applications and changed method to retrieve process names (to ensure image file extension remains present). 2019-11-22 14:58:34 +01:00
dbuechel
8d0fe0086b SEBWIN-312: Improved process creation mechanism by only using p/invoke if necessary. 2019-11-22 08:57:57 +01:00
dbuechel
8714320808 SEBWIN-314: Fixed two minor issues in legacy filter implementation within config tool. 2019-11-21 16:10:30 +01:00
dbuechel
48511d996c SEBWIN-312: Ensured application start failure does not crash SEB and clarified URL filter specification. 2019-11-21 16:07:13 +01:00
dbuechel
0a939e19ac SEBWIN-312: Ensured system task view is never activated and improved pause mechanism for activators. 2019-11-21 08:45:38 +01:00
dbuechel
d7a4dc8782 SEBWIN-312: Implemented auto-start mechanism for applications. 2019-11-20 15:30:53 +01:00
dbuechel
5ccbd2aae4 SEBWIN-312: Implemented task view for mobile UI. 2019-11-20 14:45:33 +01:00
dbuechel
fbe03b86ea SEBWIN-312: Finished first draft of task view. 2019-11-20 10:45:08 +01:00
dbuechel
08bf49b61b SEBWIN-312: Started implementing task view. 2019-11-15 16:00:03 +01:00
dbuechel
5f31656649 SEBWIN-312: Implemented scaffolding for task view and its keyboard activator. Finally consolidated keyboard and mouse hooks and resolved dependency from WindowsApi to UI. 2019-11-14 14:03:43 +01:00
dbuechel
4f930a26d8 SEBWIN-312: Fixed event handling for external application instances. 2019-11-13 11:43:34 +01:00
dbuechel
8dacc208ea SEBWIN-312: Improved stability and usability of log window. 2019-11-13 11:24:57 +01:00
dbuechel
35dd3dd4c2 SEBWIN-312: Implemented basic handling of whitelisted applications. 2019-11-13 10:11:11 +01:00
dbuechel
3d55bd6ff4 SEBWIN-312: Optimized design of application instances in action center. 2019-11-12 09:55:56 +01:00
dbuechel
b4d8af716f SEBWIN-344: Minor changes in readme. 2019-11-08 10:03:01 +01:00
dbuechel
93a3f6ab58 SEBWIN-344: Updated readme for new platform-dependent build setup. 2019-11-08 09:25:17 +01:00
dbuechel
7900a985d4 SEBWIN-344: Switched to move command to remove x64 setup from temporary directory on release build server. 2019-11-07 14:51:56 +01:00
dbuechel
2207f711bb SEBWIN-344: Alas! Switched to copy command for x64 setup copying on release build server. 2019-11-07 14:42:23 +01:00
dbuechel
47f9ad580e SEBWIN-344: Using md to create target directory for x64 setup on release build server. 2019-11-07 14:31:25 +01:00
dbuechel
78cce595e9 SEBWIN-344: Switched to xcopy for before and after build scripts on release build server. 2019-11-07 14:08:54 +01:00
dbuechel
7eec2dbaf9 SEBWIN-344: Second attempt to fix return value of robocopy commands on release build server. 2019-11-07 13:16:55 +01:00
dbuechel
79ba64b0a6 SEBWIN-344: Fixed robocopy return code for setup copy operation in release build server. 2019-11-07 12:04:37 +01:00
dbuechel
93ed53ec74 SEBWIN-344: Attempt to copy x64 setup to x86 folder on build server. 2019-11-07 11:48:39 +01:00
dbuechel
2b113e4db0 SEBWIN-344: Updated setup bundle to automatically install the correct setup and C++ runtime for a specific platform. 2019-11-07 10:05:59 +01:00
dbuechel
a6a90376ad SEBWIN-344: Removed AnyCPU and introduced x64 platform, adapted setup project to be built for both platforms. Changed configuration tool to .NET 4.7.2. 2019-11-06 15:45:17 +01:00
dbuechel
c21005b934 SEBWIN-312: Implemented loading of whitelisted applications into shell. 2019-11-06 08:45:37 +01:00
dbuechel
7e76b029a6 SEBWIN-312: Started implementing application factory and initialization of whitelisted applications. 2019-11-05 10:08:19 +01:00
dbuechel
2b976e8150 SEBWIN-312: Started implementing application whitelist mechanism. 2019-10-30 15:49:35 +01:00
dbuechel
f778d5b848 SEBWIN-312: Implemented mapping for configuration values of whitelisted applications. 2019-10-30 11:08:42 +01:00
dbuechel
cf28a7f172 SEBWIN-311: Fixed automatic termination of Windows Explorer. 2019-10-29 16:09:25 +01:00
dbuechel
b6dbe6451d SEBWIN-313: Implemented lock screen mechanism for blacklisted processes. 2019-10-11 15:46:15 +02:00
dbuechel
de6cb5e75c SEBWIN-313: Finished blacklist monitoring. 2019-10-09 14:04:27 +02:00
dbuechel
99aa595543 Fixed foreground color of log window and small scroll bar. 2019-10-09 09:54:35 +02:00
dbuechel
d3d98c7df7 SEBWIN-313: Started implementing blacklist monitoring. 2019-10-08 16:11:19 +02:00
dbuechel
8d0d1832a9 SEBWIN-311: Removed timeout for client initialization procedure to avoid application termination if user doesn't provide input within timeout. 2019-10-08 10:03:58 +02:00
dbuechel
b72c37273e SEBWIN-313: Started implementing application blacklist mechanism. 2019-10-04 16:36:12 +02:00
dbuechel
51570ecc91 Fixed status layout in readme. 2019-10-02 08:35:29 +02:00
dbuechel
863537803c Updated readme due to new release build server installation. 2019-10-02 08:15:02 +02:00
dbuechel
3efd7fbbd0 SEBWIN-311: Moved all client controller dependencies to the client context and made context available to all client operations. 2019-10-01 16:24:10 +02:00
dbuechel
8fd22032b6 SEBWIN-311: Started implementing scaffolding for third-party applications & monitoring. Renamed ApplicationSettings to AppSettings, resolved dependency from WindowsApi on Monitoring namespace and introduced ClientContext for runtime data of the client. 2019-10-01 11:30:53 +02:00
dbuechel
b2013412dd Removed safeguard question before reconfiguration. 2019-09-25 08:45:20 +02:00
dbuechel
6511401fa4 Fixed race condition between service and runtime, occurring when the runtime initiated a new session before the service was able to terminate the currently running one. 2019-09-25 08:25:51 +02:00
dbuechel
0159b9c0de SEBWIN-314: Added functionality to automatically allow start URL when request filtering is active. 2019-09-24 08:53:29 +02:00
dbuechel
f4f6b47548 Updated browser engine to version 75.1.142. Consolidated and updated nuget packages for entire solution. 2019-09-24 07:37:42 +02:00
dbuechel
d3272814bd SEBWIN-314: Completed implementation of simplified filter rule. 2019-09-20 11:45:06 +02:00
dbuechel
6d1b282b33 SEBWIN-314: Started implementing filter rules with unit tests. 2019-09-18 16:15:26 +02:00
dbuechel
5209103c97 SEBWIN-314: Completed infrastructure for browser request filtering. 2019-09-13 09:17:14 +02:00
dbuechel
d51422e188 SEBWIN-314: Main requests are now entirely prevented (before any loading happens) and only content requests are intercepted with custom resource handling. 2019-09-12 12:31:17 +02:00
dbuechel
ceba0e8a2f Only log remaining battery time if not connected to the grid. 2019-09-10 11:57:15 +02:00
dbuechel
dda48396b3 Unified naming and comments for settings. 2019-09-10 11:53:30 +02:00
dbuechel
1dd65e1bda SEBWIN-314: Completed infrastructure for request filter. 2019-09-10 11:01:49 +02:00
dbuechel
367ebf1329 SEBWIN-342: Moved settings to separate assembly. 2019-09-06 09:39:28 +02:00
dbuechel
5f5209622e SEBWIN-342: Moved settings into individiual namespaces according to their purpose. 2019-09-06 08:32:29 +02:00
dbuechel
b8fd96a10c SEBWIN-314: Started implementing scaffolding for request filter. 2019-09-06 08:13:27 +02:00
dbuechel
db390aebaf SEBWIN-342: Separated monitoring contracts into individual namespaces. 2019-09-05 09:00:41 +02:00
dbuechel
c282623eb4 SEBWIN-314: Started implementing request filter. 2019-09-05 08:38:59 +02:00
dbuechel
88afdb7d72 SEBWIN-342: Moved application instance identifier back to application namespace. 2019-09-04 15:49:48 +02:00
dbuechel
f1de26cb64 SEBWIN-342: Minor name change of release build artifacts. 2019-09-04 15:36:07 +02:00
dbuechel
66cefac874 SEBWIN-342: Resolved UI dependencies in configuration download mechanism. 2019-09-04 15:12:59 +02:00
dbuechel
363f751f55 SEBWIN-342: Removed UI dependencies from notifications. 2019-09-04 14:11:19 +02:00
dbuechel
12f44edc0b SEBWIN-342: Removed unnecessary dependency from system components on I18n. 2019-09-04 12:07:32 +02:00
dbuechel
6f51d266cc SEBWIN-342: Removed UI dependencies from wireless network system component. 2019-09-04 11:46:30 +02:00
dbuechel
dcbdc13338 SEBWIN-342: Removed UI dependencies from power supply system component. 2019-09-03 11:46:36 +02:00
dbuechel
7506ebaf10 SEBWIN-342: Removed UI dependencies from audio system component. 2019-08-30 17:33:28 +02:00
dbuechel
d8752b5558 SEBWIN-342: Removed UI dependencies from keyboard system control. 2019-08-30 15:59:51 +02:00
dbuechel
487e89693a Resolved dependencies from communication API on UI. 2019-08-30 14:02:36 +02:00
dbuechel
affd5de6a7 Resolved dependencies from browser API on UI. 2019-08-30 12:30:00 +02:00
dbuechel
fd20d0d638 Finally did what should have been done a long time ago: Moved contracts from SafeExamBrowser.Contracts to new contracts assembly per namespace. 2019-08-30 09:55:26 +02:00
dbuechel
938afcb4a7 SEBWIN-314: Started with network filter implementation. 2019-08-29 10:29:19 +02:00
dbuechel
d54d7c17dc SEBWIN-301, SEBWIN-322: Improved service feature configuration with retry mechanism. 2019-08-28 09:54:52 +02:00
dbuechel
18e6b366a5 SEBWIN-141: Removed custom windows style for good, as it does not fix the convoluted touch activation issue. 2019-08-27 11:53:29 +02:00
dbuechel
0bca109ad8 SEBWIN-141: Made workaround for initial touch activation of browser control permanent, as there appears to be no other way to fix the issue. 2019-08-27 11:25:38 +02:00
dbuechel
41d2345405 SEBWIN-301, SEBWIN-322: Increased timeout of status changes for service feature configurations. It appears like Windows Update occasionally needs more than 5 seconds to stop. 2019-08-27 11:12:31 +02:00
dbuechel
f8518eb8c4 SEBWIN-141: Removed workaround for initial touch activation of previous browser version. 2019-08-27 10:19:01 +02:00
dbuechel
c39f7febc0 Updated browser engine to version 75.1.141. 2019-08-23 15:48:23 +02:00
dbuechel
69a2fc55f4 Consolidated nuget packages for entire solution. 2019-08-23 15:02:12 +02:00
dbuechel
d3fb74c5e8 SEBWIN-300: Added description to setup sign command to prevent random application name in UAC prompt. 2019-08-23 09:49:46 +02:00
dbuechel
46f57a0cbc SEBWIN-300: Added header to setup bundle theme and improved its layout. 2019-08-22 16:18:09 +02:00
dbuechel
d4fc6a81a0 SEBWIN-300: Fixed error 0x80070001 (happening when executing the signed setup) by using the designated build targets of WiX for both setup and bundle. 2019-08-22 13:32:56 +02:00
dbuechel
3c1d6b38fe SEBWIN-338: Removed configuration of MSBuild path for release build server. 2019-08-22 09:09:58 +02:00
dbuechel
527a0a8a46 SEBWIN-300: Integrated signing of binaries and setup files into release build configuration. 2019-08-22 08:55:20 +02:00
dbuechel
9526ad2820 Fixed layout of readme. 2019-08-21 10:24:39 +02:00
dbuechel
b176ff8e2b SEBWIN-338: Updated readme according to new CI setup. 2019-08-21 10:16:34 +02:00
dbuechel
7db0ed7137 SEBWIN-338: Changed artifact names for release build. 2019-08-21 09:34:33 +02:00
dbuechel
a751e564b0 SEBWIN-338: Removed binaries from release build artifacts. 2019-08-21 09:26:07 +02:00
dbuechel
6500ea46e9 SEBWIN-338: Updated readme for new CI setup. 2019-08-20 11:54:18 +02:00
dbuechel
9eeaa8db5c SEBWIN-338: Disabled tests for release build server. 2019-08-20 11:35:26 +02:00
dbuechel
1b070af5ca SEBWIN-338: Attempt to locate msbuild on release server. 2019-08-20 11:25:50 +02:00
dbuechel
3f1473c76c SEBWIN-338: Attempt to fix missing nuget command on release server. 2019-08-20 10:30:06 +02:00
dbuechel
78b65cdd10 SEBWIN-338: Mixed up worker image for release and test build servers... :/ 2019-08-20 09:59:56 +02:00
dbuechel
25b4ce7fce SEBWIN-338: Fixed worker image for release build server. 2019-08-20 09:56:46 +02:00
dbuechel
0b307dd3e8 SEBWIN-338: Fixed version for test build server. 2019-08-20 09:31:15 +02:00
dbuechel
efbb373630 SEBWIN-338: Setting up new release build server. 2019-08-20 09:29:39 +02:00
dbuechel
f25516e858 Improved thread-safety for system components. 2019-08-16 15:45:56 +02:00
dbuechel
46a3452d93 SEBWIN-338: Still doesn't work, disabling gcov for good. 2019-08-16 13:49:28 +02:00
dbuechel
c82605bf4e SEBWIN-338: Fixed code coverage integration, re-enabling gcov. 2019-08-16 13:44:16 +02:00
dbuechel
742bf152e2 SEBWIN-338: 2nd attempt to fix code coverage aspect of CI build. 2019-08-16 13:33:25 +02:00
dbuechel
4a862bcb47 SEBWIN-338: Attempt to fix code coverage aspect of CI build by disabling gcov. 2019-08-16 12:10:49 +02:00
dbuechel
7e3eb22c93 SEBWIN-303: Improved status message for audio volume change. 2019-08-16 10:42:53 +02:00
dbuechel
06eca3c8f0 SEBWIN-303: Updated unit tests for audio component & system control. 2019-08-16 10:25:58 +02:00
dbuechel
ff38f8f36d Fixed font sizes for about window of mobile UI. 2019-08-16 10:16:11 +02:00
dbuechel
c7217944a3 SEBWIN-303: Implemented audio controls for mobile UI. 2019-08-16 10:08:10 +02:00
dbuechel
2e05e21d37 SEBWIN-337: Removed ancient TODO and suppressed all warnings for atrocious legacy configuration tool. 2019-08-16 09:34:29 +02:00
dbuechel
106651fc2d SEBWIN-303: Implemented audio control for action center. 2019-08-16 09:19:32 +02:00
dbuechel
758b61084a SEBWIN-303: Implemented audio settings. 2019-08-16 08:26:11 +02:00
dbuechel
4381be2647 SEBWIN-303: Implemented audio control logic. 2019-08-15 16:16:51 +02:00
dbuechel
cc6710cedf SEBWIN-300, SEBWIN-303: Suppressed harvesting of registry and COM elements for WiX setup (due to new dependency on NAudio). 2019-08-15 12:32:25 +02:00
dbuechel
768336e381 SEBWIN-303: Started implementing audio control. 2019-08-15 10:46:47 +02:00
dbuechel
b71529da31 SEBWIN-338: Introduced program build version. 2019-08-13 10:02:05 +02:00
dbuechel
6a1632ee48 SEBWIN-319: Forgot user-specific registry key for power options. 2019-08-13 08:29:48 +02:00
dbuechel
e1b467d984 SEBWIN-300: Fixed UI issue in setup bundle. 2019-08-09 12:32:48 +02:00
dbuechel
6d328d0501 SEBWIN-300: Integrated custom action into setup to remove leftover files from legacy installations. 2019-08-09 11:54:24 +02:00
dbuechel
79fec35b56 SEBWIN-300: Added custom user interface theme for setup bundle. 2019-08-08 15:49:54 +02:00
dbuechel
e4b74328ea Attempt to fix CI build failure due to missing build output of configuration tool. 2019-08-08 08:17:18 +02:00
dbuechel
3a12b365cb SEBWIN-300, SEBWIN-337: Completed integration of configuration tool into setup and build process. 2019-08-08 07:55:05 +02:00
dbuechel
f2c30f7a16 SEBWIN-337: Defined configuration setup resources. Build actions still missing. 2019-08-07 16:13:23 +02:00
dbuechel
6cfa5f06c8 SEBWIN-337: Fixed assembly info for legacy configuration tool. 2019-08-07 15:51:15 +02:00
dbuechel
b8ea068d17 SEBWIN-337: It is with great pain that I must declare in this here commit message that the utter abomination which is the legacy configuration tool now is integrated into the solution. May it soon become obsolete for good and be swiftly exterminated thereafter! 2019-08-07 10:10:37 +02:00
dbuechel
9f9b3c35ed SEBWIN-300: Added setup bundle artifact to CI build. 2019-08-06 11:58:31 +02:00
dbuechel
1581b41aa3 SEBWIN-300: Implemented basic setup bundle. 2019-08-06 11:56:22 +02:00
dbuechel
68f3f352fe SEBWIN-300: Added service to installer. 2019-07-31 09:51:06 +02:00
dbuechel
ff0f85d9f9 SEBWIN-300: Added SEB file extension and URL protocols to installer. 2019-07-30 15:45:20 +02:00
dbuechel
18ac56f1c6 SEBWIN-300: Added reset utility to installer. 2019-07-26 16:13:57 +02:00
dbuechel
b26c77e578 SEBWIN-300: Attempt to fix variables in CI after build script. 2019-07-26 13:54:25 +02:00
dbuechel
efada37cd7 SEBWIN-300: Fixed path of setup artifact. 2019-07-26 13:46:44 +02:00
dbuechel
0c1ed78a58 SEBWIN-300: Changed build script to include version in setup file name. 2019-07-26 13:33:01 +02:00
dbuechel
1ec6388e36 SEBWIN-300: Fixed artifact path of setup. 2019-07-26 13:06:26 +02:00
dbuechel
18352bb4c8 SEBWIN-300: Extended setup to use file version as product version and added setup artifact to CI build. 2019-07-26 12:58:07 +02:00
dbuechel
0f639aa550 SEBWIN-300: Ensured output directories of browser, client and runtime are completely emptied on clean and disabled building of setup project for debug configuration. 2019-07-26 10:06:04 +02:00
dbuechel
b42f34d684 SEBWIN-300: Added license and installer icon. 2019-07-26 09:47:14 +02:00
dbuechel
cabfe90a0e SEBWIN-300: Started implementing installer. 2019-07-25 11:29:40 +02:00
dbuechel
d9f546aa74 SEBWIN-320: Made mutex names application-wide constants and fixed unit test for lockdown operation. 2019-07-19 10:56:32 +02:00
dbuechel
8d0c83998c SEBWIN-320: Added application manifest for reset utility in order to automatically request admin privileges. Removed unused elements from client and runtime manifests. 2019-07-19 10:28:49 +02:00
dbuechel
68d487dd46 SEBWIN-320: Implemented configuration reset functionality for reset utility. 2019-07-19 10:07:45 +02:00
dbuechel
2754cdcc56 SEBWIN-320: Implemented progress animation for reset utility. 2019-07-18 10:36:41 +02:00
dbuechel
86a3e9ce3c SEBWIN-320: Implemented restore functionality for reset utility. 2019-07-17 16:17:20 +02:00
dbuechel
85c88da6cc SEBWIN-320: Extended CI build script for reset utility artifact. 2019-07-16 16:12:52 +02:00
dbuechel
fbe6f45d78 SEBWIN-320: Implemented version step for reset utility. 2019-07-16 16:09:12 +02:00
dbuechel
a27723dc35 SEBWIN-320: Implemented scroll mechanism for log step. 2019-07-16 15:43:10 +02:00
dbuechel
71854b33d8 SEBWIN-320: Implemented scaffolding for reset utility as console application. 2019-07-16 14:39:59 +02:00
dbuechel
58043a2ed9 SEBWIN-320: Change of plan, implementing reset utility as console application for now... 2019-07-16 11:40:35 +02:00
dbuechel
95a41ae55f SEBWIN-320: Implemented scaffolding for reset utility. 2019-07-12 16:17:45 +02:00
dbuechel
9dece7e4ae SEBWIN-322: Implemented service configuration infrastructure and windows update service deactivation. 2019-07-10 08:20:07 +02:00
dbuechel
0d793dd326 SEBWIN-301, SEBWIN-319: Implemented monitoring mechanism for feature configurations. 2019-07-05 12:28:42 +02:00
dbuechel
6f0b0d0fb2 SEBWIN-301: Changed service procedure so that the service initiates a system configuration update on command from the runtime. Added functionality to terminate the application on service connection loss. 2019-07-04 09:12:28 +02:00
dbuechel
39b63218fb SEBWIN-319: Implemented system configuration update for service operation and auto-restore mechanism. 2019-07-03 12:27:02 +02:00
dbuechel
2f510096d0 SEBWIN-319: Implemented registry configuration functionality. 2019-07-03 08:59:27 +02:00
dbuechel
ee683af63c SEBWIN-319: Implemented infrastructure for registry configurations and corrected mistake in backup mechanism. 2019-07-02 10:35:40 +02:00
dbuechel
ac5791ee13 SEBWIN-329: Fixed bug with custom URI scheme handling and blocked mailto URLs. 2019-06-27 15:02:35 +02:00
dbuechel
27d74b201f SEBWIN-329: Changed browser toolbar height if URL textbox is not visible in mobile UI. 2019-06-27 14:34:50 +02:00
dbuechel
c122f088c9 SEBWIN-301: Added missing test case for configuration backup repository. 2019-06-27 13:41:31 +02:00
dbuechel
3a95bb054b SEBWIN-301: Implemented unit tests for configuration backup repository. 2019-06-27 12:33:47 +02:00
dbuechel
3338710949 SEBWIN-301: Extended unit tests for service and related functionality. 2019-06-27 10:47:41 +02:00
dbuechel
dd78bc1fbc SEBWIN-301: Implemented auto-restore mechanism for service, added return status to feature configuration methods and introduced session flag for service. 2019-06-27 08:32:37 +02:00
dbuechel
f8111857db SEBWIN-301: Added unit test project for lockdown assembly. 2019-06-26 10:19:19 +02:00
dbuechel
dd801245d2 SEBWIN-301: Finished basic backup mechanism for service. 2019-06-26 10:13:11 +02:00
dbuechel
b96bbfcd78 SEBWIN-301: Started implementing backup mechanism for service. 2019-06-21 15:05:31 +02:00
dbuechel
1c7c856c33 SEBWIN-301: Defined settings for service component. 2019-06-20 10:55:24 +02:00
dbuechel
4087db9097 SEBWIN-301: Added missing test condition for session initalization operation. 2019-06-19 16:00:34 +02:00
dbuechel
ac28eec94a SEBWIN-301: Added unit tests for basic service functionality. 2019-06-19 15:40:21 +02:00
dbuechel
3589b92b9d SEBWIN-301: Implemented service log file persistence in user directory. 2019-06-18 15:51:35 +02:00
dbuechel
6b24554abc SEBWIN-301: Implemented basic service session procedure. 2019-06-18 10:18:56 +02:00
dbuechel
77a3b50ca9 SEBWIN-301: Moved ServiceOperation in session sequence of runtime to be able to interact with the user and consolidated KioskModeTerminationOperation into KioskModeOperation itself. 2019-06-12 08:46:10 +02:00
dbuechel
97bf224b37 SEBWIN-301: Fixed test coverage for session operation. 2019-06-11 11:45:05 +02:00
dbuechel
e9d91cb898 SEBWIN-301: Switched from interface to data container for session configuration (separated client and service session configuration) and implemented mapping of service policy including the respective message boxes. 2019-06-11 09:53:33 +02:00
dbuechel
73c7e28a33 SEBWIN-301: Started working on service architecture. 2019-06-07 15:26:03 +02:00
dbuechel
ccf7727d4c SEBWIN-301: Fixed usage of application data folder (local for large files vs. roaming for configuration) and implemented basic service operation for runtime. 2019-06-06 15:44:03 +02:00
dbuechel
96bad137e5 SEBWIN-301: Fixed build platform for new assemblies, as always... :/ 2019-05-29 10:58:08 +02:00
dbuechel
0bdf5d15e1 SEBWIN-301: Implemented scaffolding for service component and added CI build artifact for service. 2019-05-29 10:52:46 +02:00
dbuechel
be6332c336 Updated build script for base version. 2019-05-28 08:07:23 +02:00
1381 changed files with 104085 additions and 14906 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
[*.{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

35
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View file

@ -0,0 +1,35 @@
---
name: Bug Report
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.
> - 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.
**Steps to Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**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 problem here.

View file

@ -0,0 +1,20 @@
---
name: Feature Request
about: Suggest an idea or new feature for Safe Exam Browser.
title: ''
labels: ''
assignees: dbuechel
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

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

@ -1,21 +1,26 @@
# seb-win-refactoring
# Safe Exam Browser, Version 3.x
Refactored version of Safe Exam Browser for Windows with Chromium as integrated browser engine.
**PLEASE NOTE**\
This version is work in progress and not yet meant to be used by the public. See https://github.com/SafeExamBrowser/seb-win/releases for the current, official release of SEB.
## Requirements
## Status & Current Build
[![Build Status](https://ci.appveyor.com/api/projects/status/f1iknxq4qmtjjkj3?svg=true)](https://ci.appveyor.com/project/dbuechel/seb-win-refactoring)
[![Unit Test Status](https://img.shields.io/appveyor/tests/dbuechel/seb-win-refactoring.svg)](https://ci.appveyor.com/project/dbuechel/seb-win-refactoring/build/tests)
[![Code Coverage](https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring/branch/master/graph/badge.svg)](https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring)
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.
**Disclaimer**\
Development builds may be unstable and should thus _never_ be used in a production environment!
* .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
**Requirements**\
SEB 3.x requires these prerequisites in order to work correctly:
* .NET Framework 4.7.2 Runtime: https://dotnet.microsoft.com/download/dotnet-framework/net472
* Visual C++ Runtime Redistributable (x86, 2015): https://www.microsoft.com/en-us/download/details.aspx?id=53840
## Project Status
**Download**\
Download the latest build here: https://ci.appveyor.com/project/dbuechel/seb-win-refactoring/build/artifacts
> [!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 |
| ----------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| 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

@ -0,0 +1,17 @@
/*
* 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.Core.Contracts.Resources.Icons;
namespace SafeExamBrowser.Applications.Contracts.Events
{
/// <summary>
/// Event handler used to indicate that an icon has changed.
/// </summary>
public delegate void IconChangedEventHandler(IconResource icon);
}

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.Applications.Contracts.Events
{
/// <summary>
/// Event handler used to indicate that a title has changed to a new value.
/// </summary>
public delegate void TitleChangedEventHandler(string title);
}

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.Applications.Contracts.Events
{
/// <summary>
/// Event handler used to indicate that the windows of an application have changed.
/// </summary>
public delegate void WindowsChangedEventHandler();
}

View file

@ -0,0 +1,31 @@
/*
* 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.Applications.Contracts
{
/// <summary>
/// Defines all possible results of an attempt to create an application.
/// </summary>
public enum FactoryResult
{
/// <summary>
/// An error occurred while trying to create the application.
/// </summary>
Error,
/// <summary>
/// The application could not be found on the system.
/// </summary>
NotFound,
/// <summary>
/// The application has been created successfully.
/// </summary>
Success
}
}

View file

@ -0,0 +1,71 @@
/*
* 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 SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
namespace SafeExamBrowser.Applications.Contracts
{
/// <summary>
/// Controls the lifetime and functionality of an application.
/// </summary>
public interface IApplication<out TWindow> where TWindow : IApplicationWindow
{
/// <summary>
/// Indicates whether the application should be automatically started.
/// </summary>
bool AutoStart { get; }
/// <summary>
/// The resource providing the application icon.
/// </summary>
IconResource Icon { get; }
/// <summary>
/// The unique identifier of the application.
/// </summary>
Guid Id { get; }
/// <summary>
/// The name of the application.
/// </summary>
string Name { get; }
/// <summary>
/// The tooltip for the application.
/// </summary>
string Tooltip { get; }
/// <summary>
/// Event fired when the windows of the application have changed.
/// </summary>
event WindowsChangedEventHandler WindowsChanged;
/// <summary>
/// Returns all windows of the application.
/// </summary>
IEnumerable<TWindow> GetWindows();
/// <summary>
/// Performs any initialization work, if necessary.
/// </summary>
void Initialize();
/// <summary>
/// Starts the execution of the application.
/// </summary>
void Start();
/// <summary>
/// Performs any termination work, e.g. releasing of used resources.
/// </summary>
void Terminate();
}
}

View file

@ -0,0 +1,23 @@
/*
* 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.Settings.Applications;
namespace SafeExamBrowser.Applications.Contracts
{
/// <summary>
/// Provides functionality to create external applications.
/// </summary>
public interface IApplicationFactory
{
/// <summary>
/// Attempts to create an application according to the given settings.
/// </summary>
FactoryResult TryCreate(WhitelistApplication settings, out IApplication<IApplicationWindow> application);
}
}

View file

@ -0,0 +1,50 @@
/*
* 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 SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
namespace SafeExamBrowser.Applications.Contracts
{
/// <summary>
/// Defines a window of an <see cref="IApplication{TWindow}"/>.
/// </summary>
public interface IApplicationWindow
{
/// <summary>
/// The native handle of the window.
/// </summary>
IntPtr Handle { get; }
/// <summary>
/// The icon of the window.
/// </summary>
IconResource Icon { get; }
/// <summary>
/// The title of the window.
/// </summary>
string Title { get; }
/// <summary>
/// Event fired when the icon of the window has changed.
/// </summary>
event IconChangedEventHandler IconChanged;
/// <summary>
/// Event fired when the title of the window has changed.
/// </summary>
event TitleChangedEventHandler TitleChanged;
/// <summary>
/// Brings the window to the foreground and activates it.
/// </summary>
void Activate();
}
}

View file

@ -4,11 +4,11 @@ using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SafeExamBrowser.Contracts")]
[assembly: AssemblyTitle("SafeExamBrowser.Applications.Contracts")]
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Contracts")]
[assembly: AssemblyCopyright("Copyright © 2019 ETH Zürich, Educational Development and Technology (LET)")]
[assembly: AssemblyProduct("SafeExamBrowser.Applications.Contracts")]
[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
@ -16,7 +16,7 @@ using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("47da5933-bef8-4729-94e6-abde2db12262")]
[assembly: Guid("ac77745d-3b41-43e2-8e84-d40e5a4ee77f")]
// Version information for an assembly consists of the following four values:
//

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<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>{AC77745D-3B41-43E2-8E84-D40E5A4EE77F}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.Applications.Contracts</RootNamespace>
<AssemblyName>SafeExamBrowser.Applications.Contracts</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</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>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</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>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<Compile Include="Events\IconChangedEventHandler.cs" />
<Compile Include="Events\TitleChangedEventHandler.cs" />
<Compile Include="Events\WindowsChangedEventHandler.cs" />
<Compile Include="FactoryResult.cs" />
<Compile Include="IApplication.cs" />
<Compile Include="IApplicationFactory.cs" />
<Compile Include="IApplicationWindow.cs" />
<Compile Include="Properties\AssemblyInfo.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>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

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

@ -0,0 +1,147 @@
/*
* 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.IO;
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 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,
IRegistry registry)
{
this.applicationMonitor = applicationMonitor;
this.logger = logger;
this.nativeMethods = nativeMethods;
this.processFactory = processFactory;
this.registry = registry;
}
public FactoryResult TryCreate(WhitelistApplication settings, out IApplication<IApplicationWindow> application)
{
var name = $"'{settings.DisplayName}' ({settings.ExecutableName})";
application = default;
try
{
var success = TryFindApplication(settings, out var executablePath);
if (success)
{
application = BuildApplication(executablePath, settings);
application.Initialize();
logger.Debug($"Successfully initialized application {name}.");
return FactoryResult.Success;
}
logger.Error($"Could not find application {name}!");
return FactoryResult.NotFound;
}
catch (Exception e)
{
logger.Error($"Unexpected error while trying to initialize application {name}!", e);
}
return FactoryResult.Error;
}
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, ONE_SECOND);
return application;
}
private bool TryFindApplication(WhitelistApplication settings, out string mainExecutable)
{
var paths = new List<string[]>();
var registryPath = QueryPathFromRegistry(settings);
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)
{
paths.Add(new[] { settings.ExecutablePath, settings.ExecutableName });
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), settings.ExecutablePath, settings.ExecutableName });
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), settings.ExecutablePath, settings.ExecutableName });
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.System), settings.ExecutablePath, settings.ExecutableName });
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), settings.ExecutablePath, settings.ExecutableName });
}
if (registryPath != default)
{
paths.Add(new[] { registryPath, settings.ExecutableName });
if (settings.ExecutablePath != default)
{
paths.Add(new[] { registryPath, settings.ExecutablePath, settings.ExecutableName });
}
}
foreach (var path in paths)
{
try
{
mainExecutable = Path.Combine(path);
mainExecutable = Environment.ExpandEnvironmentVariables(mainExecutable);
if (File.Exists(mainExecutable))
{
return true;
}
}
catch (Exception e)
{
logger.Error($"Failed to test path {string.Join(@"\", path)}!", e);
}
}
return false;
}
private string QueryPathFromRegistry(WhitelistApplication settings)
{
if (registry.TryRead($@"{RegistryValue.MachineHive.AppPaths_Key}\{settings.ExecutableName}", "Path", out var value))
{
return value as string;
}
return default;
}
}
}

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.Applications.Events
{
internal delegate void InstanceTerminatedEventHandler(int id);
}

View file

@ -0,0 +1,174 @@
/*
* 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 SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Settings.Applications;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications
{
internal class ExternalApplication : IApplication<IApplicationWindow>
{
private readonly object @lock = new object();
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; }
public Guid Id { get; private set; }
public string Name { get; private set; }
public string Tooltip { get; private set; }
public event WindowsChangedEventHandler WindowsChanged;
internal ExternalApplication(
IApplicationMonitor applicationMonitor,
string executablePath,
IModuleLogger logger,
INativeMethods nativeMethods,
IProcessFactory processFactory,
WhitelistApplication settings,
int windowMonitoringInterval_ms)
{
this.applicationMonitor = applicationMonitor;
this.executablePath = executablePath;
this.logger = logger;
this.nativeMethods = nativeMethods;
this.instances = new List<ExternalApplicationInstance>();
this.processFactory = processFactory;
this.settings = settings;
this.windowMonitoringInterval = windowMonitoringInterval_ms;
}
public IEnumerable<IApplicationWindow> GetWindows()
{
lock (@lock)
{
return instances.SelectMany(i => i.GetWindows());
}
}
public void Initialize()
{
AutoStart = settings.AutoStart;
Icon = new EmbeddedIconResource { FilePath = executablePath };
Id = settings.Id;
Name = settings.DisplayName;
Tooltip = settings.Description ?? settings.DisplayName;
applicationMonitor.InstanceStarted += ApplicationMonitor_InstanceStarted;
}
public void Start()
{
try
{
logger.Info("Starting application...");
InitializeInstance(processFactory.StartNew(executablePath, BuildArguments()));
logger.Info("Successfully started application.");
}
catch (Exception e)
{
logger.Error("Failed to start application!", e);
}
}
public void Terminate()
{
applicationMonitor.InstanceStarted -= ApplicationMonitor_InstanceStarted;
try
{
lock (@lock)
{
if (instances.Any() && !settings.AllowRunning)
{
logger.Info($"Terminating application with {instances.Count} instance(s)...");
foreach (var instance in instances)
{
instance.Terminated -= Instance_Terminated;
instance.Terminate();
}
logger.Info("Successfully terminated application.");
}
}
}
catch (Exception e)
{
logger.Error($"Failed to terminate application!", e);
}
}
private void ApplicationMonitor_InstanceStarted(Guid applicationId, IProcess process)
{
lock (@lock)
{
var isNewInstance = instances.All(i => i.Id != process.Id);
if (applicationId == Id && isNewInstance)
{
logger.Info("New application instance was started.");
InitializeInstance(process);
}
}
}
private void Instance_Terminated(int id)
{
lock (@lock)
{
instances.Remove(instances.First(i => i.Id == id));
}
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, windowMonitoringInterval);
instance.Terminated += Instance_Terminated;
instance.WindowsChanged += () => WindowsChanged?.Invoke();
instance.Initialize();
instances.Add(instance);
}
}
}
}

View file

@ -0,0 +1,174 @@
/*
* 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.Timers;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Applications.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications
{
internal class ExternalApplicationInstance
{
private readonly object @lock = new object();
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;
internal int Id { get; private set; }
internal event InstanceTerminatedEventHandler Terminated;
internal event WindowsChangedEventHandler WindowsChanged;
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>();
}
internal IEnumerable<IApplicationWindow> GetWindows()
{
lock (@lock)
{
return new List<IApplicationWindow>(windows);
}
}
internal void Initialize()
{
Id = process.Id;
InitializeEvents();
logger.Info("Initialized application instance.");
}
internal void Terminate()
{
const int MAX_ATTEMPTS = 5;
const int TIMEOUT_MS = 500;
var terminated = process.HasTerminated;
if (terminated)
{
logger.Info("Application instance is already terminated.");
}
else
{
FinalizeEvents();
for (var attempt = 0; attempt < MAX_ATTEMPTS && !terminated; attempt++)
{
terminated = process.TryClose(TIMEOUT_MS);
}
for (var attempt = 0; attempt < MAX_ATTEMPTS && !terminated; attempt++)
{
terminated = process.TryKill(TIMEOUT_MS);
}
if (terminated)
{
logger.Info("Successfully terminated application instance.");
}
else
{
logger.Warn("Failed to terminate application instance!");
}
}
}
private void Process_Terminated(int exitCode)
{
logger.Info($"Application instance has terminated with exit code {exitCode}.");
FinalizeEvents();
Terminated?.Invoke(Id);
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
var changed = false;
var openWindows = nativeMethods.GetOpenWindows();
lock (@lock)
{
var closedWindows = windows.Where(w => openWindows.All(ow => ow != w.Handle)).ToList();
var openedWindows = openWindows.Where(ow => windows.All(w => w.Handle != ow) && BelongsToInstance(ow)).ToList();
foreach (var window in closedWindows)
{
changed = true;
windows.Remove(window);
}
foreach (var window in openedWindows)
{
changed = true;
windows.Add(new ExternalApplicationWindow(icon, nativeMethods, window));
}
foreach (var window in windows)
{
window.Update();
}
}
if (changed)
{
WindowsChanged?.Invoke();
}
timer.Start();
}
private bool BelongsToInstance(IntPtr window)
{
return nativeMethods.GetProcessIdFor(window) == process.Id;
}
private void InitializeEvents()
{
process.Terminated += Process_Terminated;
timer = new Timer(windowMonitoringInterval);
timer.Elapsed += Timer_Elapsed;
timer.Start();
}
private void FinalizeEvents()
{
if (timer != default)
{
timer.Elapsed -= Timer_Elapsed;
timer.Stop();
}
process.Terminated -= Process_Terminated;
}
}
}

View file

@ -0,0 +1,60 @@
/*
* 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 SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications
{
internal class ExternalApplicationWindow : IApplicationWindow
{
private readonly INativeMethods nativeMethods;
public IntPtr Handle { get; }
public IconResource Icon { get; private set; }
public string Title { get; private set; }
public event IconChangedEventHandler IconChanged;
public event TitleChangedEventHandler TitleChanged;
internal ExternalApplicationWindow(IconResource icon, INativeMethods nativeMethods, IntPtr handle)
{
this.Handle = handle;
this.Icon = icon;
this.nativeMethods = nativeMethods;
}
public void Activate()
{
nativeMethods.ActivateWindow(Handle);
}
internal void Update()
{
var icon = nativeMethods.GetWindowIcon(Handle);
var iconChanged = icon != IntPtr.Zero && (!(Icon is NativeIconResource) || Icon is NativeIconResource r && r.Handle != icon);
var title = nativeMethods.GetWindowTitle(Handle);
var titleChanged = Title?.Equals(title, StringComparison.Ordinal) != true;
if (iconChanged)
{
Icon = new NativeIconResource { Handle = icon };
IconChanged?.Invoke(Icon);
}
if (titleChanged)
{
Title = title;
TitleChanged?.Invoke(title);
}
}
}
}

View file

@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SafeExamBrowser.Applications")]
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Applications")]
[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")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0.0")]

View file

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<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>{A113E68F-1209-4689-981A-15C554B2DF4E}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.Applications</RootNamespace>
<AssemblyName>SafeExamBrowser.Applications</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</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>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</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>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<Compile Include="ApplicationFactory.cs" />
<Compile Include="Events\InstanceTerminatedEventHandler.cs" />
<Compile Include="ExternalApplication.cs" />
<Compile Include="ExternalApplicationInstance.cs" />
<Compile Include="ExternalApplicationWindow.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</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.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="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -1,14 +1,12 @@
/*
* Copyright (c) 2019 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.Contracts.UserInterface.Browser;
namespace SafeExamBrowser.Contracts.Browser
namespace SafeExamBrowser.Browser.Contracts.Events
{
/// <summary>
/// The event arguments used for all download events.
@ -20,11 +18,6 @@ namespace SafeExamBrowser.Contracts.Browser
/// </summary>
public bool AllowDownload { get; set; }
/// <summary>
/// The browser window from which the download request originated.
/// </summary>
public IBrowserWindow BrowserWindow { get; set; }
/// <summary>
/// Callback executed once a download has been finished.
/// </summary>
@ -34,5 +27,10 @@ namespace SafeExamBrowser.Contracts.Browser
/// The full path under which the specified file should be saved.
/// </summary>
public string DownloadPath { get; set; }
/// <summary>
/// The URL of the resource to be downloaded.
/// </summary>
public string Url { get; set; }
}
}

View file

@ -1,16 +1,16 @@
/*
* Copyright (c) 2019 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/.
*/
namespace SafeExamBrowser.Contracts.Browser
namespace SafeExamBrowser.Browser.Contracts.Events
{
/// <summary>
/// Defines the method signature for callbacks to be executed once a download has been finished. Indicates whether the download was
/// successful, and if so, where it was saved.
/// Defines the method signature for callbacks to be executed once a download has been finished. Indicates the URL of the resource,
/// whether the download was successful, and if so, where it was saved.
/// </summary>
public delegate void DownloadFinishedCallback(bool success, string filePath = null);
public delegate void DownloadFinishedCallback(bool success, string url, string filePath = null);
}

View file

@ -1,12 +1,12 @@
/*
* Copyright (c) 2019 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/.
*/
namespace SafeExamBrowser.Contracts.Browser
namespace SafeExamBrowser.Browser.Contracts.Events
{
/// <summary>
/// Event handler used to control (e.g. allow or prohibit) download requests.

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

@ -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 a termination request has been detected.
/// </summary>
public delegate void TerminationRequestedEventHandler();
}

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 browser has detected a user identifier of an LMS.
/// </summary>
public delegate void UserIdentifierDetectedEventHandler(string identifier);
}

View file

@ -0,0 +1,33 @@
/*
* 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.Settings.Browser.Filter;
namespace SafeExamBrowser.Browser.Contracts.Filters
{
/// <summary>
/// Defines the filter for browser requests.
/// </summary>
public interface IRequestFilter
{
/// <summary>
/// The default result to be returned by <see cref="Process(Request)"/> if no rule matches.
/// </summary>
FilterResult Default { get; set; }
/// <summary>
/// Loads the given filter rule to be used when processing requests.
/// </summary>
void Load(IRule rule);
/// <summary>
/// Filters the given request according to the loaded rules.
/// </summary>
FilterResult Process(Request request);
}
}

View file

@ -0,0 +1,33 @@
/*
* 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.Settings.Browser.Filter;
namespace SafeExamBrowser.Browser.Contracts.Filters
{
/// <summary>
/// Defines a request filter rule.
/// </summary>
public interface IRule
{
/// <summary>
/// The filter result to be used if the rule matches a request.
/// </summary>
FilterResult Result { get; }
/// <summary>
/// Initializes the rule for processing requests.
/// </summary>
void Initialize(FilterRuleSettings settings);
/// <summary>
/// Indicates whether the rule applies for the given request.
/// </summary>
bool IsMatch(Request request);
}
}

View file

@ -0,0 +1,23 @@
/*
* 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.Settings.Browser.Filter;
namespace SafeExamBrowser.Browser.Contracts.Filters
{
/// <summary>
/// Builds request filter rules.
/// </summary>
public interface IRuleFactory
{
/// <summary>
/// Creates a filter rule for the given type.
/// </summary>
IRule CreateRule(FilterRuleType type);
}
}

View file

@ -0,0 +1,21 @@
/*
* 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.Filters
{
/// <summary>
/// Holds data relevant for filtering requests.
/// </summary>
public class Request
{
/// <summary>
/// The full URL of the request.
/// </summary>
public string Url { get; set; }
}
}

View file

@ -0,0 +1,45 @@
/*
* 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;
using SafeExamBrowser.Browser.Contracts.Events;
namespace SafeExamBrowser.Browser.Contracts
{
/// <summary>
/// Controls the lifetime and functionality of the browser application.
/// </summary>
public interface IBrowserApplication : IApplication<IBrowserWindow>
{
/// <summary>
/// Event fired when the browser application detects a download request for an application configuration file.
/// </summary>
event DownloadRequestedEventHandler ConfigurationDownloadRequested;
/// <summary>
/// Event fired when the user tries to focus the taskbar.
/// </summary>
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

@ -0,0 +1,33 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SafeExamBrowser.Browser.Contracts")]
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Browser.Contracts")]
[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)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("5fb5273d-277c-41dd-8593-a25ce1aff2e9")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0.0")]

View file

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<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>{5FB5273D-277C-41DD-8593-A25CE1AFF2E9}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.Browser.Contracts</RootNamespace>
<AssemblyName>SafeExamBrowser.Browser.Contracts</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</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>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</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>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<Compile Include="Events\DownloadEventArgs.cs" />
<Compile Include="Events\DownloadFinishedCallback.cs" />
<Compile Include="Events\DownloadRequestedEventHandler.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>
<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.Settings\SafeExamBrowser.Settings.csproj">
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
<Name>SafeExamBrowser.Settings</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,553 @@
/*
* 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
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Text;
using System.Text.RegularExpressions;
namespace SafeExamBrowser.Browser.UnitTests.Filters
{
internal class LegacyFilter
{
internal Regex scheme;
internal Regex user;
internal Regex password;
internal Regex host;
internal int? port;
internal Regex path;
internal Regex query;
internal Regex fragment;
internal LegacyFilter(string filterExpressionString)
{
SEBURLFilterExpression URLFromString = new SEBURLFilterExpression(filterExpressionString);
try
{
this.scheme = RegexForFilterString(URLFromString.scheme);
this.user = RegexForFilterString(URLFromString.user);
this.password = RegexForFilterString(URLFromString.password);
this.host = RegexForHostFilterString(URLFromString.host);
this.port = URLFromString.port;
this.path = RegexForPathFilterString(URLFromString.path);
this.query = RegexForQueryFilterString(URLFromString.query);
this.fragment = RegexForFilterString(URLFromString.fragment);
}
catch (Exception)
{
throw;
}
}
// Method comparing all components of a passed URL with the filter expression
// and returning YES (= allow or block) if it matches
internal bool IsMatch(Uri URLToFilter)
{
Regex filterComponent;
// If a scheme is indicated in the filter expression, it has to match
filterComponent = scheme;
UriBuilder urlToFilterParts = new UriBuilder(URLToFilter);
if (filterComponent != null &&
!Regex.IsMatch(URLToFilter.Scheme, filterComponent.ToString(), RegexOptions.IgnoreCase))
{
// Scheme of the URL to filter doesn't match the one from the filter expression: Exit with matching = NO
return false;
}
string userInfo = URLToFilter.UserInfo;
filterComponent = user;
if (filterComponent != null &&
!Regex.IsMatch(urlToFilterParts.UserName, filterComponent.ToString(), RegexOptions.IgnoreCase))
{
return false;
}
filterComponent = password;
if (filterComponent != null &&
!Regex.IsMatch(urlToFilterParts.Password, filterComponent.ToString(), RegexOptions.IgnoreCase))
{
return false;
}
filterComponent = host;
if (filterComponent != null &&
!Regex.IsMatch(URLToFilter.Host, filterComponent.ToString(), RegexOptions.IgnoreCase))
{
return false;
}
if (port != null && URLToFilter.Port != port)
{
return false;
}
filterComponent = path;
if (filterComponent != null &&
!Regex.IsMatch(URLToFilter.AbsolutePath.TrimEnd(new char[] { '/' }), filterComponent.ToString(), RegexOptions.IgnoreCase))
{
return false;
}
string urlQuery = URLToFilter.GetComponents(UriComponents.Query, UriFormat.Unescaped);
filterComponent = query;
if (filterComponent != null)
{
// If there's a query filter component, then we need to even filter empty URL query strings
// as the filter might either allow some specific queries or no query at all ("?." query filter)
if (urlQuery == null)
{
urlQuery = "";
}
if (!Regex.IsMatch(urlQuery, filterComponent.ToString(), RegexOptions.IgnoreCase))
{
return false;
}
}
string urlFragment = URLToFilter.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
filterComponent = fragment;
if (filterComponent != null &&
!Regex.IsMatch(urlFragment, filterComponent.ToString(), RegexOptions.IgnoreCase))
{
return false;
}
// URL matches the filter expression
return true;
}
internal static Regex RegexForFilterString(string filterString)
{
if (string.IsNullOrEmpty(filterString))
{
return null;
}
else
{
string regexString = Regex.Escape(filterString);
regexString = regexString.Replace("\\*", ".*?");
// Add regex command characters for matching at start and end of a line (part)
regexString = string.Format("^{0}$", regexString);
try
{
Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
return regex;
}
catch (Exception)
{
throw;
}
}
}
internal static Regex RegexForHostFilterString(string filterString)
{
if (string.IsNullOrEmpty(filterString))
{
return null;
}
else
{
try
{
// Check if host string has a dot "." prefix to disable subdomain matching
if (filterString.Length > 1 && filterString.StartsWith("."))
{
// Get host string without the "." prefix
filterString = filterString.Substring(1);
// Get regex for host <*://example.com> (without possible subdomains)
return RegexForFilterString(filterString);
}
// Allow subdomain matching: Create combined regex for <example.com> and <*.example.com>
string regexString = Regex.Escape(filterString);
regexString = regexString.Replace("\\*", ".*?");
// Add regex command characters for matching at start and end of a line (part)
regexString = string.Format("^(({0})|(.*?\\.{0}))$", regexString);
Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
return regex;
}
catch (Exception)
{
throw;
}
}
}
internal static Regex RegexForPathFilterString(string filterString)
{
// Trim a possible trailing slash "/", we will instead add a rule to also match paths to directories without trailing slash
filterString = filterString.TrimEnd(new char[] { '/' });
;
if (string.IsNullOrEmpty(filterString))
{
return null;
}
else
{
try
{
// Check if path string ends with a "/*" for matching contents of a directory
if (filterString.EndsWith("/*"))
{
// As the path filter string matches for a directory, we need to add a string to match directories without trailing slash
// Get path string without the "/*" suffix
string filterStringDirectory = filterString.Substring(0, filterString.Length - 2);
string regexString = Regex.Escape(filterString);
regexString = regexString.Replace("\\*", ".*?");
string regexStringDir = Regex.Escape(filterString);
regexStringDir = regexStringDir.Replace("\\*", ".*?");
// Add regex command characters for matching at start and end of a line (part)
regexString = string.Format("^(({0})|({1}))$", regexString, regexStringDir);
Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
return regex;
}
else
{
return RegexForFilterString(filterString);
}
}
catch (Exception)
{
throw;
}
}
}
internal static Regex RegexForQueryFilterString(string filterString)
{
if (string.IsNullOrEmpty(filterString))
{
return null;
}
else
{
if (filterString.Equals("."))
{
// Add regex command characters for matching at start and end of a line (part)
// and regex for no string allowed
string regexString = @"^$";
try
{
Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
return regex;
}
catch (Exception)
{
throw;
}
}
else
{
return RegexForFilterString(filterString);
}
}
}
public override string ToString()
{
StringBuilder expressionString = new StringBuilder();
string part;
expressionString.Append("^");
/// Scheme
if (this.scheme != null)
{
// If there is a regex filter for scheme
// get stripped regex pattern
part = StringForRegexFilter(this.scheme);
}
else
{
// otherwise use the regex wildcard pattern for scheme
part = @".*?";
}
expressionString.AppendFormat("{0}:\\/\\/", part);
/// User/Password
if (this.user != null)
{
part = StringForRegexFilter(this.user);
expressionString.Append(part);
if (this.password != null)
{
expressionString.AppendFormat(":{0}@", StringForRegexFilter(this.password));
}
else
{
expressionString.Append("@");
}
}
/// Host
string hostPort = "";
if (this.host != null)
{
hostPort = StringForRegexFilter(this.host);
}
else
{
hostPort = ".*?";
}
/// Port
if (this.port != null && this.port > 0 && this.port <= 65535)
{
hostPort = string.Format("{0}:{1}", hostPort, this.port);
}
// When there is a host, but no path
if (this.host != null && this.path == null)
{
hostPort = string.Format("(({0})|({0}\\/.*?))", hostPort);
}
expressionString.Append(hostPort);
/// Path
if (this.path != null)
{
string path = StringForRegexFilter(this.path);
if (path.StartsWith("\\/"))
{
expressionString.Append(path);
}
else
{
expressionString.AppendFormat("\\/{0}", path);
}
}
/// Query
if (this.query != null)
{
// Check for special case Query = "?." which means no query string is allowed
if (StringForRegexFilter(this.query).Equals("."))
{
expressionString.AppendFormat("[^\\?]");
}
else
{
expressionString.AppendFormat("\\?{0}", StringForRegexFilter(this.query));
}
}
else
{
expressionString.AppendFormat("(()|(\\?.*?))");
}
/// Fragment
if (this.fragment != null)
{
expressionString.AppendFormat("#{0}", StringForRegexFilter(this.fragment));
}
expressionString.Append("$");
return expressionString.ToString();
}
internal string StringForRegexFilter(Regex regexFilter)
{
// Get pattern string from regular expression
string regexPattern = regexFilter.ToString();
if (regexPattern.Length <= 2)
{
return "";
}
// Remove the regex command characters for matching at start and end of a line
regexPattern = regexPattern.Substring(1, regexPattern.Length - 2);
return regexPattern;
}
private class SEBURLFilterExpression
{
internal string scheme;
internal string user;
internal string password;
internal string host;
internal int? port;
internal string path;
internal string query;
internal string fragment;
internal SEBURLFilterExpression(string filterExpressionString)
{
if (!string.IsNullOrEmpty(filterExpressionString))
{
/// Convert Uri to a SEBURLFilterExpression
string splitURLRegexPattern = @"(?:([^\:]*)\:\/\/)?(?:([^\:\@]*)(?:\:([^\@]*))?\@)?(?:([^\/\:]*))?(?:\:([0-9\*]*))?([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?";
Regex splitURLRegex = new Regex(splitURLRegexPattern);
Match regexMatch = splitURLRegex.Match(filterExpressionString);
if (regexMatch.Success == false)
{
return;
}
this.scheme = regexMatch.Groups[1].Value;
this.user = regexMatch.Groups[2].Value;
this.password = regexMatch.Groups[3].Value;
this.host = regexMatch.Groups[4].Value;
// Treat a special case when a query or fragment is interpreted as part of the host address
if (this.host.Contains("?") || this.host.Contains("#"))
{
string splitURLRegexPattern2 = @"([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?";
Regex splitURLRegex2 = new Regex(splitURLRegexPattern2);
Match regexMatch2 = splitURLRegex2.Match(this.host);
if (regexMatch.Success == false)
{
return;
}
this.host = regexMatch2.Groups[1].Value;
this.port = null;
this.path = "";
this.query = regexMatch2.Groups[2].Value;
this.fragment = regexMatch2.Groups[3].Value;
}
else
{
string portNumber = regexMatch.Groups[5].Value;
// We only want a port if the filter expression string explicitely defines one!
if (portNumber.Length == 0 || portNumber == "*")
{
this.port = null;
}
else
{
this.port = UInt16.Parse(portNumber);
}
this.path = regexMatch.Groups[6].Value.TrimEnd(new char[] { '/' });
this.query = regexMatch.Groups[7].Value;
this.fragment = regexMatch.Groups[8].Value;
}
}
}
internal static string User(string userInfo)
{
string user = "";
if (!string.IsNullOrEmpty(userInfo))
{
int userPasswordSeparator = userInfo.IndexOf(":");
if (userPasswordSeparator == -1)
{
user = userInfo;
}
else
{
if (userPasswordSeparator != 0)
{
user = userInfo.Substring(0, userPasswordSeparator);
}
}
}
return user;
}
internal static string Password(string userInfo)
{
string password = "";
if (!string.IsNullOrEmpty(userInfo))
{
int userPasswordSeparator = userInfo.IndexOf(":");
if (userPasswordSeparator != -1)
{
if (userPasswordSeparator < userInfo.Length - 1)
{
password = userInfo.Substring(userPasswordSeparator + 1, userInfo.Length - 1 - userPasswordSeparator);
}
}
}
return password;
}
internal SEBURLFilterExpression(string scheme, string user, string password, string host, int port, string path, string query, string fragment)
{
this.scheme = scheme;
this.user = user;
this.password = password;
this.host = host;
this.port = port;
this.path = path;
this.query = query;
this.fragment = fragment;
}
public override string ToString()
{
StringBuilder expressionString = new StringBuilder();
if (!string.IsNullOrEmpty(this.scheme))
{
if (!string.IsNullOrEmpty(this.host))
{
expressionString.AppendFormat("{0}://", this.scheme);
}
else
{
expressionString.AppendFormat("{0}:", this.scheme);
}
}
if (!string.IsNullOrEmpty(this.user))
{
expressionString.Append(this.user);
if (!string.IsNullOrEmpty(this.password))
{
expressionString.AppendFormat(":{0}@", this.password);
}
else
{
expressionString.Append("@");
}
}
if (!string.IsNullOrEmpty(this.host))
{
expressionString.Append(this.host);
}
if (this.port != null && this.port > 0 && this.port <= 65535)
{
expressionString.AppendFormat(":{0}", this.port);
}
if (!string.IsNullOrEmpty(this.path))
{
if (this.path.StartsWith("/"))
{
expressionString.Append(this.path);
}
else
{
expressionString.AppendFormat("/{0}", this.path);
}
}
if (!string.IsNullOrEmpty(this.query))
{
expressionString.AppendFormat("?{0}", this.query);
}
if (!string.IsNullOrEmpty(this.fragment))
{
expressionString.AppendFormat("#{0}", this.fragment);
}
return expressionString.ToString();
}
}
}
}

View file

@ -0,0 +1,114 @@
/*
* 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.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Filters;
using SafeExamBrowser.Settings.Browser.Filter;
namespace SafeExamBrowser.Browser.UnitTests.Filters
{
[TestClass]
public class RequestFilterTests
{
private RequestFilter sut;
[TestInitialize]
public void Initialize()
{
sut = new RequestFilter();
}
[TestMethod]
public void MustProcessBlockRulesFirst()
{
var allow = new Mock<IRule>();
var block = new Mock<IRule>();
allow.SetupGet(r => r.Result).Returns(FilterResult.Allow);
block.SetupGet(r => r.Result).Returns(FilterResult.Block);
block.Setup(r => r.IsMatch(It.IsAny<Request>())).Returns(true);
sut.Load(allow.Object);
sut.Load(block.Object);
var result = sut.Process(new Request());
allow.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Never);
block.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Once);
Assert.AreEqual(FilterResult.Block, result);
}
[TestMethod]
public void MustProcessAllowRulesSecond()
{
var allow = new Mock<IRule>();
var block = new Mock<IRule>();
allow.SetupGet(r => r.Result).Returns(FilterResult.Allow);
allow.Setup(r => r.IsMatch(It.IsAny<Request>())).Returns(true);
block.SetupGet(r => r.Result).Returns(FilterResult.Block);
sut.Load(allow.Object);
sut.Load(block.Object);
var result = sut.Process(new Request());
allow.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Once);
block.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Once);
Assert.AreEqual(FilterResult.Allow, result);
}
[TestMethod]
public void MustReturnDefaultWithoutMatch()
{
var allow = new Mock<IRule>();
var block = new Mock<IRule>();
allow.SetupGet(r => r.Result).Returns(FilterResult.Allow);
block.SetupGet(r => r.Result).Returns(FilterResult.Block);
sut.Default = (FilterResult) (-1);
sut.Load(allow.Object);
sut.Load(block.Object);
var result = sut.Process(new Request());
allow.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Once);
block.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Once);
Assert.AreEqual((FilterResult) (-1), result);
}
[TestMethod]
public void MustReturnDefaultWithoutRules()
{
sut.Default = FilterResult.Allow;
var result = sut.Process(new Request());
Assert.AreEqual(FilterResult.Allow, result);
sut.Default = FilterResult.Block;
result = sut.Process(new Request());
Assert.AreEqual(FilterResult.Block, result);
}
[TestMethod]
[ExpectedException(typeof(NotImplementedException))]
public void MustNotAllowUnsupportedResult()
{
var rule = new Mock<IRule>();
rule.SetupGet(r => r.Result).Returns((FilterResult) (-1));
sut.Load(rule.Object);
}
}
}

View file

@ -0,0 +1,42 @@
/*
* 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 SafeExamBrowser.Browser.Filters;
using SafeExamBrowser.Browser.Filters.Rules;
using SafeExamBrowser.Settings.Browser.Filter;
namespace SafeExamBrowser.Browser.UnitTests.Filters
{
[TestClass]
public class RuleFactoryTests
{
private RuleFactory sut;
[TestInitialize]
public void Initialize()
{
sut = new RuleFactory();
}
[TestMethod]
public void MustCreateCorrectRules()
{
Assert.IsInstanceOfType(sut.CreateRule(FilterRuleType.Regex), typeof(RegexRule));
Assert.IsInstanceOfType(sut.CreateRule(FilterRuleType.Simplified), typeof(SimplifiedRule));
}
[TestMethod]
[ExpectedException(typeof(NotImplementedException))]
public void MustNotAllowUnsupportedFilterType()
{
sut.CreateRule((FilterRuleType) (-1));
}
}
}

View file

@ -0,0 +1,67 @@
/*
* 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.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Filters.Rules;
using SafeExamBrowser.Settings.Browser.Filter;
namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
{
[TestClass]
public class RegexRuleTests
{
private RegexRule sut;
[TestInitialize]
public void Initialize()
{
sut = new RegexRule();
}
[TestMethod]
public void MustIgnoreCase()
{
sut.Initialize(new FilterRuleSettings { Expression = Regex.Escape("http://www.test.org/path/file.txt?param=123") });
Assert.IsTrue(sut.IsMatch(new Request { Url = "hTtP://wWw.TeSt.OrG/pAtH/fIlE.tXt?PaRaM=123" }));
Assert.IsTrue(sut.IsMatch(new Request { Url = "HtTp://WwW.tEst.oRg/PaTh/FiLe.TxT?pArAm=123" }));
sut.Initialize(new FilterRuleSettings { Expression = Regex.Escape("HTTP://WWW.TEST.ORG/PATH/FILE.TXT?PARAM=123") });
Assert.IsTrue(sut.IsMatch(new Request { Url = "hTtP://wWw.TeSt.OrG/pAtH/fIlE.tXt?PaRaM=123" }));
Assert.IsTrue(sut.IsMatch(new Request { Url = "HtTp://WwW.tEst.oRg/PaTh/FiLe.TxT?pArAm=123" }));
}
[TestMethod]
public void MustInitializeResult()
{
foreach (var result in Enum.GetValues(typeof(FilterResult)))
{
sut.Initialize(new FilterRuleSettings { Expression = "", Result = (FilterResult) result });
Assert.AreEqual(result, sut.Result);
}
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void MustNotAllowUndefinedExpression()
{
sut.Initialize(new FilterRuleSettings());
}
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void MustValidateExpression()
{
sut.Initialize(new FilterRuleSettings { Expression = "ç+\"}%&*/(+)=?{=*+¦]@#°§]`?´^¨'°[¬|¢" });
}
}
}

View file

@ -0,0 +1,962 @@
/*
* 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 Microsoft.VisualStudio.TestTools.UnitTesting;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Filters.Rules;
using SafeExamBrowser.Settings.Browser.Filter;
namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
{
[TestClass]
public class SimplifiedRuleTests
{
private SimplifiedRule sut;
[TestInitialize]
public void Initialize()
{
sut = new SimplifiedRule();
}
[TestMethod]
public void MustIgnoreCase()
{
sut.Initialize(new FilterRuleSettings { Expression = "http://www.test.org/path/file.txt?param=123" });
Assert.IsTrue(sut.IsMatch(new Request { Url = "hTtP://wWw.TeSt.OrG/pAtH/fIlE.tXt?PaRaM=123" }));
Assert.IsTrue(sut.IsMatch(new Request { Url = "HtTp://WwW.tEst.oRg/PaTh/FiLe.TxT?pArAm=123" }));
sut.Initialize(new FilterRuleSettings { Expression = "HTTP://WWW.TEST.ORG/PATH/FILE.TXT?PARAM=123" });
Assert.IsTrue(sut.IsMatch(new Request { Url = "hTtP://wWw.TeSt.OrG/pAtH/fIlE.tXt?PaRaM=123" }));
Assert.IsTrue(sut.IsMatch(new Request { Url = "HtTp://WwW.tEst.oRg/PaTh/FiLe.TxT?pArAm=123" }));
}
[TestMethod]
public void MustInitializeResult()
{
foreach (var result in Enum.GetValues(typeof(FilterResult)))
{
sut.Initialize(new FilterRuleSettings { Expression = "*", Result = (FilterResult) result });
Assert.AreEqual(result, sut.Result);
}
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void MustNotAllowUndefinedExpression()
{
sut.Initialize(new FilterRuleSettings());
}
[TestMethod]
public void MustValidateExpression()
{
var invalid = new[]
{
".", "+", "\"", "ç", "%", "&", "/", "(", ")", "=", "?", "^", "!", "[", "]", "{", "}", "¦", "@", "#", "°", "§", "¬", "|", "¢", "´", "'", "`", "~", "<", ">", "\\"
};
sut.Initialize(new FilterRuleSettings { Expression = "*" });
sut.Initialize(new FilterRuleSettings { Expression = "a" });
sut.Initialize(new FilterRuleSettings { Expression = "A" });
sut.Initialize(new FilterRuleSettings { Expression = "0" });
sut.Initialize(new FilterRuleSettings { Expression = "abcdeFGHIJK-12345" });
foreach (var expression in invalid)
{
Assert.ThrowsException<ArgumentException>(() => sut.Initialize(new FilterRuleSettings { Expression = expression }));
}
}
[TestMethod]
public void TestAlphanumericExpression()
{
var expression = "hostname123";
var positive = new[]
{
$"scheme://{expression}"
};
var negative = new[]
{
$"scheme://hostname",
$"scheme://hostname1",
$"scheme://hostname12",
$"scheme://hostname1234",
$"scheme://{expression}.org",
$"scheme://www.{expression}.org",
$"scheme://subdomain.{expression}.com",
$"scheme://www.realhost.{expression}",
$"scheme://subdomain-1.subdomain-2.{expression}.org",
$"scheme://user:password@www.{expression}.org/path/file.txt?param=123#fragment",
$"scheme://{expression}4",
$"scheme://hostname.org",
$"scheme://hostname12.org",
$"scheme://{expression}4.org",
$"scheme://{expression}.realhost.org",
$"scheme://subdomain.{expression}.realhost.org",
$"{expression}://www.host.org",
$"scheme://www.host.org/{expression}/path",
$"scheme://www.host.org/path?param={expression}",
$"scheme://{expression}:password@www.host.org",
$"scheme://user:{expression}@www.host.org",
$"scheme://user:password@www.host.org/path?param=123#{expression}"
};
Test(expression, positive, negative, false);
}
[TestMethod]
public void TestAlphanumericExpressionWithWildcard()
{
var expression = "hostname*";
var positive = new[]
{
"scheme://hostname.org",
"scheme://hostnameabc.org",
"scheme://hostname-12.org",
"scheme://hostname-abc-def-123-456.org",
"scheme://www.hostname-abc.org",
"scheme://www.realhost.hostname",
"scheme://subdomain.hostname-xyz.com",
"scheme://hostname.realhost.org",
"scheme://subdomain.hostname.realhost.org",
"scheme://subdomain-1.subdomain-2.hostname-abc-123.org",
"scheme://user:password@www.hostname-abc.org/path/file.txt?param=123#fragment"
};
var negative = new[]
{
"scheme://hostnam.org",
"hostname://www.host.org",
"scheme://www.host.org/hostname/path",
"scheme://www.host.org/path?param=hostname",
"scheme://hostname:password@www.host.org",
"scheme://user:hostname@www.host.org",
"scheme://user:password@www.host.org/path?param=123#hostname"
};
Test(expression, positive, negative, false);
}
[TestMethod]
public void TestHostWithDomain()
{
var expression = "123-hostname.org";
var positive = new[]
{
$"scheme://{expression}",
$"scheme://www.{expression}",
$"scheme://subdomain.{expression}",
$"scheme://subdomain-1.subdomain-2.{expression}",
$"scheme://user:password@www.{expression}/path/file.txt?param=123#fragment"
};
var negative = new[]
{
$"scheme://123.org",
$"scheme://123-host.org",
$"scheme://{expression}.com",
$"scheme://{expression}s.org",
$"scheme://{expression}.realhost.org",
$"scheme://subdomain.{expression}.realhost.org",
$"scheme{expression}://www.host.org",
$"scheme://www.host.org/{expression}/path",
$"scheme://www.host.org/path?param={expression}",
$"scheme://{expression}:password@www.host.org",
$"scheme://user:{expression}@www.host.org",
$"scheme://user:password@www.host.org/path?param=123#{expression}"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestHostWithWildcard()
{
var expression = "test.*.org";
var positive = new[]
{
"scheme://test.host.org",
"scheme://test.host.domain.org",
"scheme://subdomain.test.host.org",
"scheme://user:password@test.domain.org/path/file.txt?param=123#fragment"
};
var negative = new[]
{
"scheme://test.org",
"scheme://host.com/test.host.org",
"scheme://www.host.org/test.host.org/path",
"scheme://www.host.org/path?param=test.host.org",
"scheme://test.host.org:password@www.host.org",
"scheme://user:test.host.org@www.host.org",
"scheme://user:password@www.host.org/path?param=123#test.host.org"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestHostWithWildcardAsSuffix()
{
var expression = "test.host.*";
var positive = new[]
{
"scheme://test.host.org",
"scheme://test.host.domain.org",
"scheme://subdomain.test.host.org",
"scheme://user:password@test.host.org/path/file.txt?param=123#fragment"
};
var negative = new[]
{
"scheme://host.com",
"scheme://test.host",
"scheme://host.com/test.host.txt"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestHostWithWildcardAsPrefix()
{
var expression = "*.org";
var positive = new[]
{
"scheme://domain.org",
"scheme://test.host.org",
"scheme://test.host.domain.org",
"scheme://user:password@www.host.org/path/file.txt?param=123#fragment"
};
var negative = new[]
{
"scheme://org",
"scheme://host.com",
"scheme://test.net",
"scheme://test.ch",
"scheme://host.com/test.org"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestHostWithExactSubdomain()
{
var expression = ".www.host.org";
var positive = new[]
{
"scheme://www.host.org",
"scheme://user:password@www.host.org/path/file.txt?param=123#fragment"
};
var negative = new[]
{
"scheme://host.org",
"scheme://test.www.host.org",
"scheme://www.host.org.com"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestHostWithTrailingSlash()
{
var expression = "host.org/";
var positive = new[]
{
"scheme://host.org",
"scheme://host.org/",
"scheme://host.org/url",
"scheme://host.org/url/",
"scheme://host.org/url/path",
"scheme://host.org/url/path/",
"scheme://user:password@www.host.org/url/path?param=123#fragment",
"scheme://user:password@www.host.org/url/path/?param=123#fragment"
};
Test(expression, positive, Array.Empty<string>());
}
[TestMethod]
public void TestHostWithoutTrailingSlash()
{
var expression = "host.org";
var positive = new[]
{
"scheme://host.org",
"scheme://host.org/",
"scheme://host.org/url",
"scheme://host.org/url/",
"scheme://host.org/url/path",
"scheme://host.org/url/path/",
"scheme://user:password@www.host.org/url/path?param=123#fragment",
"scheme://user:password@www.host.org/url/path/?param=123#fragment"
};
Test(expression, positive, Array.Empty<string>());
}
[TestMethod]
public void TestPortNumber()
{
var expression = "host.org:2020";
var positive = new[]
{
"scheme://host.org:2020",
"scheme://www.host.org:2020",
"scheme://user:password@www.host.org:2020/path/file.txt?param=123#fragment"
};
var negative = new[]
{
"scheme://host.org",
"scheme://www.host.org",
"scheme://www.host.org:2",
"scheme://www.host.org:20",
"scheme://www.host.org:202",
"scheme://www.host.org:20202"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestPortWildcard()
{
var expression = "host.org:*";
var positive = new[]
{
"scheme://host.org",
"scheme://host.org:0",
"scheme://host.org:1",
"scheme://host.org:2020",
"scheme://host.org:65535",
"scheme://www.host.org",
"scheme://www.host.org:2",
"scheme://www.host.org:20",
"scheme://www.host.org:202",
"scheme://www.host.org:2020",
"scheme://www.host.org:20202",
"scheme://user:password@www.host.org:2020/path/file.txt?param=123#fragment"
};
Test(expression, positive, Array.Empty<string>());
}
[TestMethod]
public void TestPortNumberWithHostWildcard()
{
var expression = "*:2020";
var positive = new[]
{
"scheme://host.org:2020",
"scheme://domain.com:2020",
"scheme://user:password@www.server.net:2020/path/file.txt?param=123#fragment"
};
var negative = new List<string>
{
"scheme://host.org"
};
for (var port = 0; port < 65536; port++)
{
if (port != 2020)
{
negative.Add($"{negative[0]}:{port}");
}
}
Test(expression, positive, negative.ToArray());
}
[TestMethod]
public void TestPath()
{
var expression = "host.org/url/path";
var positive = new[]
{
"scheme://host.org/url/path",
"scheme://user:password@www.host.org/url/path?param=123#fragment"
};
var negative = new[]
{
"scheme://host.org",
"scheme://host.org//",
"scheme://host.org///",
"scheme://host.org/url",
"scheme://host.org/path",
"scheme://host.org/url/path.txt",
"scheme://host.org/another/url/path"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestPathWithFile()
{
var expression = "host.org/url/path/to/file.txt";
var positive = new[]
{
"scheme://host.org/url/path/to/file.txt",
"scheme://subdomain.host.org/url/path/to/file.txt",
"scheme://user:password@www.host.org/url/path/to/file.txt?param=123#fragment"
};
var negative = new[]
{
"scheme://host.org",
"scheme://host.org/url",
"scheme://host.org/path",
"scheme://host.org/file.txt",
"scheme://host.org/url/path.txt",
"scheme://host.org/url/path/to.txt",
"scheme://host.org/url/path/to/file",
"scheme://host.org/url/path/to/file.",
"scheme://host.org/url/path/to/file.t",
"scheme://host.org/url/path/to/file.tx",
"scheme://host.org/url/path/to/file.txt/segment",
"scheme://host.org/url/path/to/file.txtt",
"scheme://host.org/another/url/path/to/file.txt"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestPathWithWildcard()
{
var expression = "host.org/*/path";
var positive = new[]
{
"scheme://host.org//path",
"scheme://host.org/url/path",
"scheme://host.org/another/url/path",
"scheme://user:password@www.host.org/yet/another/url/path?param=123#fragment"
};
var negative = new[]
{
"scheme://host.org",
"scheme://host.org/url",
"scheme://host.org/path",
"scheme://host.org/url/path.txt",
"scheme://host.org/url/path/2"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestPathWithHostWildcard()
{
var expression = "*/url/path";
var positive = new[]
{
"scheme://local/url/path",
"scheme://host.org/url/path",
"scheme://www.host.org/url/path",
"scheme://another.server.org/url/path",
"scheme://user:password@www.host.org/url/path?param=123#fragment"
};
var negative = new[]
{
"scheme://host.org",
"scheme://host.org/url",
"scheme://host.org/path",
"scheme://host.org/url/path.txt",
"scheme://host.org/url/path/2"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestPathWithTrailingSlash()
{
var expression = "host.org/url/path/";
var positive = new[]
{
"scheme://host.org/url/path",
"scheme://host.org/url/path/",
"scheme://user:password@www.host.org/url/path?param=123#fragment",
"scheme://user:password@www.host.org/url/path/?param=123#fragment"
};
Test(expression, positive, Array.Empty<string>());
}
[TestMethod]
public void TestPathWithoutTrailingSlash()
{
var expression = "host.org/url/path";
var positive = new[]
{
"scheme://host.org/url/path",
"scheme://host.org/url/path/",
"scheme://user:password@www.host.org/url/path?param=123#fragment",
"scheme://user:password@www.host.org/url/path/?param=123#fragment"
};
Test(expression, positive, Array.Empty<string>());
}
[TestMethod]
public void TestScheme()
{
var expression = "scheme://host.org";
var positive = new[]
{
"scheme://host.org",
"scheme://www.host.org",
"scheme://user:password@www.host.org/url/path?param=123#fragment"
};
var negative = new[]
{
"//host.org",
"https://host.org",
"ftp://host.org",
"ftps://host.org",
"schemes://host.org"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestSchemeWithWildcard()
{
var expression = "*tp://host.org";
var positive = new[]
{
"tp://host.org",
"ftp://www.host.org",
"http://user:password@www.host.org/url/path?param=123#fragment"
};
var negative = new[]
{
"//host.org",
"p://host.org",
"https://host.org",
"ftps://host.org"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestSchemeWithHostWildcard()
{
var expression = "scheme://*";
var positive = new[]
{
"scheme://host",
"scheme://www.server.org",
"scheme://subdomain.domain.org",
"scheme://user:password@www.host.org/url/path?param=123#fragment"
};
var negative = new[]
{
"//host.org",
"http://host.org",
"https://host.org",
"ftp://host.org",
"ftps://host.org",
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestUserInfoWithName()
{
var expression = "user@host.org";
var positive = new[]
{
"scheme://user@host.org",
"scheme://user@www.host.org",
"scheme://user:password@host.org",
"scheme://user:password-123@host.org",
"scheme://user:password@www.host.org/url/path?param=123#fragment"
};
var negative = new[]
{
"scheme://u@host.org",
"scheme://us@host.org",
"scheme://use@host.org",
"scheme://usera@host.org",
"scheme://user@server.net",
"scheme://usertwo@www.host.org"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestUserInfoWithNameWildcard()
{
var expression = "user*@host.org";
var positive = new[]
{
"scheme://user@host.org",
"scheme://userabc@host.org",
"scheme://user:abc@host.org",
"scheme://user-123@www.host.org",
"scheme://user-123:password@host.org",
"scheme://user-123:password-123@host.org",
"scheme://user-abc-123:password@www.host.org/url/path?param=123#fragment"
};
var negative = new[]
{
"scheme://u@host.org",
"scheme://us@host.org",
"scheme://use@host.org",
"scheme://user@server.net",
"scheme://usertwo@server.org"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestUserInfoWithPassword()
{
var expression = "user:password@host.org";
var positive = new[]
{
"scheme://user:password@host.org",
"scheme://user:password@www.host.org",
"scheme://user:password@www.host.org/url/path?param=123#fragment"
};
var negative = new[]
{
"scheme://u@host.org",
"scheme://us@host.org",
"scheme://use@host.org",
"scheme://user@server.net",
"scheme://usertwo@server.org",
"scheme://user@host.org",
"scheme://userabc@host.org",
"scheme://user:abc@host.org",
"scheme://user-123@www.host.org",
"scheme://user-123:password@host.org",
"scheme://user-123:password@www.host.org/url/path?param=123#fragment",
"scheme://user:password-123@host.org",
"scheme://user:password-123@www.host.org/url/path?param=123#fragment"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestUserInfoWithWildcard()
{
var expression = "*@host.org";
var positive = new[]
{
"scheme://host.org",
"scheme://user@host.org",
"scheme://user:password@host.org",
"scheme://www.host.org/url/path?param=123#fragment",
"scheme://user@www.host.org/url/path?param=123#fragment",
"scheme://user:password@www.host.org/url/path?param=123#fragment"
};
var negative = new[]
{
"scheme://server.org",
"scheme://user@server.org",
"scheme://www.server.org/url/path?param=123#fragment",
"scheme://user:password@www.server.org/url/path?param=123#fragment"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestUserInfoWithHostWildcard()
{
var expression = "user:password@*";
var positive = new[]
{
"scheme://user:password@host.org",
"scheme://user:password@server.net",
"scheme://user:password@www.host.org/url/path?param=123#fragment"
};
var negative = new[]
{
"scheme://host.org",
"scheme://server.org",
"scheme://user@host.org",
"scheme://user@server.org",
"scheme://password@host.org",
"scheme://www.host.org/url/path?param=123#fragment",
"scheme://www.server.org/url/path?param=123#fragment",
"scheme://user@www.host.org/url/path?param=123#fragment",
"scheme://user@www.server.org/url/path?param=123#fragment",
"scheme://password@www.server.org/url/path?param=123#fragment"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestQuery()
{
var expression = "host.org?param=123";
var positive = new[]
{
"scheme://host.org?param=123",
"scheme://www.host.org/?param=123",
"scheme://www.host.org/path/?param=123",
"scheme://www.host.org/some/other/random/path?param=123",
"scheme://user:password@www.host.org/url/path?param=123#fragment"
};
var negative = new[]
{
"scheme://host.org?",
"scheme://host.org?=",
"scheme://host.org?=123",
"scheme://host.org?param=",
"scheme://host.org?param=1",
"scheme://host.org?param=12",
"scheme://host.org?param=1234"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestQueryWithWildcardAsPrefix()
{
var expression = "host.org?*param=123";
var positive = new[]
{
"scheme://host.org?param=123",
"scheme://www.host.org?param=123",
"scheme://www.host.org/path/?param=123",
"scheme://www.host.org/some/other/random/path?param=123",
"scheme://user:password@www.host.org/url/path?param=123#fragment",
"scheme://host.org?other_param=456&param=123",
"scheme://host.org?param=123&another_param=123",
"scheme://www.host.org?other_param=456&param=123",
"scheme://www.host.org/path/?other_param=456&param=123",
"scheme://www.host.org/some/other/random/path?other_param=456&param=123",
"scheme://user:password@www.host.org/url/path?other_param=456&param=123#fragment",
"scheme://host.org?some_param=123469yvuiopwo&another_param=some%20whitespaces%26special%20characters%2B%22%2A%25%C3%A7%2F%28&param=123"
};
var negative = new[]
{
"scheme://host.org?",
"scheme://host.org?=",
"scheme://host.org?=123",
"scheme://host.org?aram=123",
"scheme://host.org?param=",
"scheme://host.org?param=1",
"scheme://host.org?param=12",
"scheme://host.org?param=1234",
"scheme://host.org?param=123&another_param=456"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestQueryWithWildcardAsSuffix()
{
var expression = "host.org?param=123*";
var positive = new[]
{
"scheme://host.org?param=123",
"scheme://host.org?param=1234",
"scheme://www.host.org?param=123",
"scheme://www.host.org/path/?param=123",
"scheme://host.org?param=123&another_param=456",
"scheme://www.host.org/some/other/random/path?param=123",
"scheme://user:password@www.host.org/url/path?param=123#fragment",
"scheme://host.org?param=123&other_param=456",
"scheme://www.host.org/path/?param=123&other_param=456",
"scheme://www.host.org/some/other/random/path?param=123&other_param=456",
"scheme://user:password@www.host.org/url/path?param=123&other_param=456#fragment",
"scheme://host.org?param=123&some_param=123469yvuiopwo&another_param=some%20whitespaces%26special%20characters%2B%22%2A%25%C3%A7%2F%28"
};
var negative = new[]
{
"scheme://host.org?",
"scheme://host.org?=",
"scheme://host.org?=123",
"scheme://host.org?aram=123",
"scheme://host.org?param=",
"scheme://host.org?param=1",
"scheme://host.org?param=12",
"scheme://host.org?aparam=123",
"scheme://www.host.org?param=456&param=123",
"scheme://host.org?some_param=123469yvuiopwo&another_param=some%20whitespaces%26special%20characters%2B%22%2A%25%C3%A7%2F%28&param=123"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestQueryNotAllowed()
{
var expression = "host.org?.";
var positive = new[]
{
"scheme://host.org",
"scheme://host.org?",
"scheme://user:password@www.host.org/url/path#fragment",
"scheme://user:password@www.host.org/url/path?#fragment"
};
var negative = new[]
{
"scheme://host.org?a",
"scheme://host.org?%20",
"scheme://host.org?=",
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestQueryWithHostWildcard()
{
var expression = "*?param=123";
var positive = new[]
{
"scheme://host.org?param=123",
"scheme://server.net?param=123",
"scheme://user:password@www.host.org/url/path?param=123#fragment",
"scheme://user:password@www.server.net/url/path?param=123#fragment"
};
var negative = new[]
{
"scheme://host.org?param=1234",
"scheme://host.org?param=12",
"scheme://host.org?",
"scheme://host.org?param",
"scheme://host.org?123",
"scheme://host.org?="
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestFragment()
{
var expression = "host.org#fragment";
var positive = new[]
{
"scheme://host.org#fragment",
"scheme://www.host.org#fragment",
"scheme://user:password@www.host.org/url/path/file.txt?param=123#fragment"
};
var negative = new[]
{
"scheme://host.org",
"scheme://host.org#",
"scheme://host.org#fragmen",
"scheme://host.org#fragment123",
"scheme://host.org#otherfragment"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestFragmentWithWildcardAsPrefix()
{
var expression = "host.org#*fragment";
var positive = new[]
{
"scheme://host.org#fragment",
"scheme://host.org#somefragment",
"scheme://www.host.org#another_fragment",
"scheme://user:password@www.host.org/url/path/file.txt?param=123#fragment"
};
var negative = new[]
{
"scheme://host.org",
"scheme://host.org#",
"scheme://host.org#fragmen",
"scheme://host.org#fragment123"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestFragmentWithWildcardAsSuffix()
{
var expression = "host.org#fragment*";
var positive = new[]
{
"scheme://host.org#fragment",
"scheme://host.org#fragment-123",
"scheme://user:password@www.host.org/url/path/file.txt?param=123#fragment_abc"
};
var negative = new[]
{
"scheme://host.org",
"scheme://host.org#",
"scheme://host.org#fragmen",
"scheme://www.host.org#another_fragment"
};
Test(expression, positive, negative);
}
[TestMethod]
public void TestFragmentWithHostWildcard()
{
var expression = "*#fragment";
var positive = new[]
{
"scheme://host.org#fragment",
"scheme://server.net#fragment",
"scheme://user:password@www.host.org/url/path?param=123#fragment",
"scheme://user:password@www.server.net/url/path?param=123#fragment"
};
var negative = new[]
{
"scheme://host.org",
"scheme://host.org#",
"scheme://host.org#fragmen",
"scheme://host.org#fragment123"
};
Test(expression, positive, negative);
}
private void Test(string expression, string[] positive, string[] negative, bool testLegacy = true)
{
var legacy = new LegacyFilter(expression);
sut.Initialize(new FilterRuleSettings { Expression = expression });
foreach (var url in positive)
{
Assert.IsTrue(sut.IsMatch(new Request { Url = url }), url);
if (testLegacy)
{
Assert.IsTrue(legacy.IsMatch(new Uri(url)), url);
}
}
foreach (var url in negative)
{
Assert.IsFalse(sut.IsMatch(new Request { Url = url }), url);
if (testLegacy)
{
Assert.IsFalse(legacy.IsMatch(new Uri(url)), url);
}
}
}
}
}

View file

@ -0,0 +1,46 @@
/*
* 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 Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Browser.Handlers;
namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
[TestClass]
public class ContextMenuHandlerTests
{
private ContextMenuHandler sut;
[TestInitialize]
public void Initialize()
{
sut = new ContextMenuHandler();
}
[TestMethod]
public void MustClearContextMenu()
{
var menu = new Mock<IMenuModel>();
sut.OnBeforeContextMenu(default(IWebBrowser), default(IBrowser), default(IFrame), default(IContextMenuParams), menu.Object);
menu.Verify(m => m.Clear(), Times.Once);
}
[TestMethod]
public void MustBlockContextMenu()
{
var command = sut.OnContextMenuCommand(default(IWebBrowser), default(IBrowser), default(IFrame), default(IContextMenuParams), default(CefMenuCommand), default(CefEventFlags));
var run = sut.RunContextMenu(default(IWebBrowser), default(IBrowser), default(IFrame), default(IContextMenuParams), default(IMenuModel), default(IRunContextMenuCallback));
Assert.IsFalse(command);
Assert.IsFalse(run);
}
}
}

View file

@ -0,0 +1,106 @@
/*
* 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.Collections.Generic;
using System.Threading;
using CefSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
[TestClass]
public class DialogHandlerTests
{
private DialogHandler sut;
[TestInitialize]
public void Initialize()
{
sut = new DialogHandler();
}
[TestMethod]
public void MustCorrectlyCancelDialog()
{
RequestDialog(default, false);
}
[TestMethod]
public void MustCorrectlyRequestOpenFileDialog()
{
var args = RequestDialog(CefFileDialogMode.Open);
Assert.AreEqual(FileSystemElement.File, args.Element);
Assert.AreEqual(FileSystemOperation.Open, args.Operation);
}
[TestMethod]
public void MustCorrectlyRequestOpenFolderDialog()
{
var args = RequestDialog(CefFileDialogMode.OpenFolder);
Assert.AreEqual(FileSystemElement.Folder, args.Element);
Assert.AreEqual(FileSystemOperation.Open, args.Operation);
}
[TestMethod]
public void MustCorrectlyRequestSaveFileDialog()
{
var args = RequestDialog(CefFileDialogMode.Save);
Assert.AreEqual(FileSystemElement.File, args.Element);
Assert.AreEqual(FileSystemOperation.Save, args.Operation);
}
private DialogRequestedEventArgs RequestDialog(CefFileDialogMode mode, bool confirm = true)
{
var args = default(DialogRequestedEventArgs);
var callback = new Mock<IFileDialogCallback>();
var title = "Some random dialog title";
var initialPath = @"C:\Some\Random\Path";
var sync = new AutoResetEvent(false);
var threadId = default(int);
callback.Setup(c => c.Cancel()).Callback(() => sync.Set());
callback.Setup(c => c.Continue(It.IsAny<List<string>>())).Callback(() => sync.Set());
sut.DialogRequested += (a) =>
{
args = a;
args.Success = confirm;
args.FullPath = @"D:\Some\Other\File\Path.txt";
threadId = Thread.CurrentThread.ManagedThreadId;
};
var status = sut.OnFileDialog(default, default, mode, title, initialPath, default, callback.Object);
sync.WaitOne();
if (confirm)
{
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<List<string>>()), Times.Never);
callback.Verify(c => c.Cancel(), Times.Once);
}
Assert.IsTrue(status);
Assert.AreEqual(initialPath, args.InitialPath);
Assert.AreEqual(title, args.Title);
Assert.AreNotEqual(threadId, Thread.CurrentThread.ManagedThreadId);
return args;
}
}
}

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.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SafeExamBrowser.Browser.Handlers;
namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
[TestClass]
public class DisplayHandlerTests
{
private DisplayHandler sut;
[TestInitialize]
public void Initialize()
{
sut = new DisplayHandler();
}
[TestMethod]
public void MustUseDefaultHandling()
{
var text = default(string);
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]
public void MustHandleFaviconChange()
{
var newUrl = "www.someurl.org/favicon.ico";
var url = default(string);
var called = false;
sut.FaviconChanged += (u) =>
{
called = true;
url = u;
};
sut.OnFaviconUrlChange(default, default, new List<string>());
Assert.AreEqual(default, url);
Assert.IsFalse(called);
sut.OnFaviconUrlChange(default, default, new List<string> { newUrl });
Assert.AreEqual(newUrl, url);
Assert.IsTrue(called);
}
[TestMethod]
public void MustHandleProgressChange()
{
var expected = 0.123456;
var actual = default(double);
sut.ProgressChanged += (p) => actual = p;
sut.OnLoadingProgressChange(default, default, expected);
Assert.AreEqual(expected, actual);
}
}
}

View file

@ -0,0 +1,311 @@
/*
* 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.IO;
using System.Threading;
using CefSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
[TestClass]
public class DownloadHandlerTests
{
private AppConfig appConfig;
private Mock<ILogger> logger;
private BrowserSettings settings;
private WindowSettings windowSettings;
private DownloadHandler sut;
[TestInitialize]
public void Initialize()
{
appConfig = new AppConfig();
logger = new Mock<ILogger>();
settings = new BrowserSettings();
windowSettings = new WindowSettings();
sut = new DownloadHandler(appConfig, logger.Object, settings, windowSettings);
}
[TestMethod]
public void MustCorrectlyHandleConfigurationByFileExtension()
{
var item = new DownloadItem
{
SuggestedFileName = "File.seb",
Url = "https://somehost.org/some-path"
};
RequestConfigurationDownload(item);
}
[TestMethod]
public void MustCorrectlyHandleConfigurationByUrlExtension()
{
var item = new DownloadItem
{
SuggestedFileName = "Abc.xyz",
Url = "https://somehost.org/some-path-to/file.seb"
};
RequestConfigurationDownload(item);
}
[TestMethod]
public void MustCorrectlyHandleConfigurationByMimeType()
{
appConfig.ConfigurationFileMimeType = "some/mime-type";
var item = new DownloadItem
{
MimeType = appConfig.ConfigurationFileMimeType,
SuggestedFileName = "Abc.xyz",
Url = "https://somehost.org/some-path"
};
RequestConfigurationDownload(item);
}
[TestMethod]
public void MustCorrectlyHandleDeniedConfigurationFileDownload()
{
var args = default(DownloadEventArgs);
var callback = new Mock<IBeforeDownloadCallback>();
var failed = false;
var fileName = default(string);
var item = new DownloadItem
{
SuggestedFileName = "File.seb",
Url = "https://somehost.org/some-path"
};
var sync = new AutoResetEvent(false);
var threadId = default(int);
settings.AllowDownloads = false;
settings.AllowConfigurationDownloads = true;
sut.ConfigurationDownloadRequested += (f, a) =>
{
args = a;
args.AllowDownload = false;
fileName = f;
threadId = Thread.CurrentThread.ManagedThreadId;
sync.Set();
};
sut.DownloadUpdated += (state) => failed = true;
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
sync.WaitOne();
callback.VerifyNoOtherCalls();
Assert.IsFalse(failed);
Assert.IsFalse(args.AllowDownload);
Assert.AreEqual(item.SuggestedFileName, fileName);
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId);
}
[TestMethod]
public void MustCorrectlyHandleFileDownload()
{
var callback = new Mock<IBeforeDownloadCallback>();
var downloadPath = default(string);
var failed = false;
var item = new DownloadItem
{
MimeType = "application/something",
SuggestedFileName = "File.txt",
Url = "https://somehost.org/somefile.abc"
};
var sync = new AutoResetEvent(false);
var threadId = default(int);
callback.Setup(c => c.Continue(It.IsAny<string>(), It.IsAny<bool>())).Callback<string, bool>((f, s) =>
{
downloadPath = f;
threadId = Thread.CurrentThread.ManagedThreadId;
sync.Set();
});
settings.AllowDownloads = true;
settings.AllowConfigurationDownloads = false;
sut.ConfigurationDownloadRequested += (f, a) => failed = true;
sut.DownloadUpdated += (state) => failed = true;
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
sync.WaitOne();
callback.Verify(c => c.Continue(It.Is<string>(p => p.Equals(downloadPath)), false), Times.Once);
Assert.IsFalse(failed);
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId);
}
[TestMethod]
public void MustCorrectlyHandleFileDownloadWithCustomDirectory()
{
var callback = new Mock<IBeforeDownloadCallback>();
var failed = false;
var item = new DownloadItem
{
MimeType = "application/something",
SuggestedFileName = "File.txt",
Url = "https://somehost.org/somefile.abc"
};
var sync = new AutoResetEvent(false);
var threadId = default(int);
callback.Setup(c => c.Continue(It.IsAny<string>(), It.IsAny<bool>())).Callback(() =>
{
threadId = Thread.CurrentThread.ManagedThreadId;
sync.Set();
});
settings.AllowDownloads = true;
settings.AllowConfigurationDownloads = false;
settings.AllowCustomDownAndUploadLocation = true;
settings.DownAndUploadDirectory = @"%APPDATA%\Downloads";
sut.ConfigurationDownloadRequested += (f, a) => failed = true;
sut.DownloadUpdated += (state) => failed = true;
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
sync.WaitOne();
var downloadPath = Path.Combine(Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory), item.SuggestedFileName);
callback.Verify(c => c.Continue(It.Is<string>(p => p.Equals(downloadPath)), true), Times.Once);
Assert.IsFalse(failed);
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId);
}
[TestMethod]
public void MustDoNothingIfDownloadsNotAllowed()
{
var callback = new Mock<IBeforeDownloadCallback>();
var fail = false;
var item = new DownloadItem
{
SuggestedFileName = "File.txt",
Url = "https://somehost.org/somefile.abc"
};
settings.AllowDownloads = false;
settings.AllowConfigurationDownloads = false;
sut.ConfigurationDownloadRequested += (file, args) => fail = true;
sut.DownloadUpdated += (state) => fail = true;
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
callback.VerifyNoOtherCalls();
Assert.IsFalse(fail);
}
[TestMethod]
public void MustUpdateDownloadProgress()
{
var callback = new Mock<IBeforeDownloadCallback>();
var failed = false;
var item = new DownloadItem
{
MimeType = "application/something",
SuggestedFileName = "File.txt",
Url = "https://somehost.org/somefile.abc"
};
var state = default(DownloadItemState);
var sync = new AutoResetEvent(false);
var threadId = default(int);
callback.Setup(c => c.Continue(It.IsAny<string>(), It.IsAny<bool>())).Callback(() => sync.Set());
settings.AllowDownloads = true;
settings.AllowConfigurationDownloads = false;
sut.ConfigurationDownloadRequested += (f, a) => failed = true;
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
sync.WaitOne();
Assert.IsFalse(failed);
sut.DownloadUpdated += (s) =>
{
state = s;
threadId = Thread.CurrentThread.ManagedThreadId;
sync.Set();
};
item.PercentComplete = 10;
sut.OnDownloadUpdated(default(IWebBrowser), default(IBrowser), item, default(IDownloadItemCallback));
sync.WaitOne();
Assert.IsFalse(state.IsCancelled);
Assert.IsFalse(state.IsComplete);
Assert.AreEqual(0.1, state.Completion);
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId);
item.PercentComplete = 20;
sut.OnDownloadUpdated(default(IWebBrowser), default(IBrowser), item, default(IDownloadItemCallback));
sync.WaitOne();
Assert.IsFalse(state.IsCancelled);
Assert.IsFalse(state.IsComplete);
Assert.AreEqual(0.2, state.Completion);
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId);
item.PercentComplete = 50;
item.IsCancelled = true;
sut.OnDownloadUpdated(default(IWebBrowser), default(IBrowser), item, default(IDownloadItemCallback));
sync.WaitOne();
Assert.IsFalse(failed);
Assert.IsTrue(state.IsCancelled);
Assert.IsFalse(state.IsComplete);
Assert.AreEqual(0.5, state.Completion);
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId);
}
private void RequestConfigurationDownload(DownloadItem item)
{
var args = default(DownloadEventArgs);
var callback = new Mock<IBeforeDownloadCallback>();
var failed = false;
var fileName = default(string);
var sync = new AutoResetEvent(false);
var threadId = default(int);
callback.Setup(c => c.Continue(It.IsAny<string>(), It.IsAny<bool>())).Callback(() => sync.Set());
settings.AllowDownloads = false;
settings.AllowConfigurationDownloads = true;
sut.ConfigurationDownloadRequested += (f, a) =>
{
args = a;
args.AllowDownload = true;
args.DownloadPath = @"C:\Downloads\File.seb";
fileName = f;
threadId = Thread.CurrentThread.ManagedThreadId;
};
sut.DownloadUpdated += (state) => failed = true;
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
sync.WaitOne();
callback.Verify(c => c.Continue(It.Is<string>(p => p.Equals(args.DownloadPath)), false), Times.Once);
Assert.IsFalse(failed);
Assert.IsTrue(args.AllowDownload);
Assert.AreEqual(item.SuggestedFileName, fileName);
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId);
}
}
}

View file

@ -0,0 +1,166 @@
/*
* 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.Windows.Forms;
using CefSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SafeExamBrowser.Browser.Handlers;
namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
[TestClass]
public class KeyboardHandlerTests
{
private KeyboardHandler sut;
[TestInitialize]
public void Initialize()
{
sut = new KeyboardHandler();
}
[TestMethod]
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);
Assert.IsTrue(reloadRequested);
Assert.IsTrue(handled);
reloadRequested = false;
handled = sut.OnPreKeyEvent(default(IWebBrowser), default(IBrowser), default(KeyType), default(int), default(int), default(CefEventFlags), default(bool), ref isShortcut);
Assert.IsFalse(reloadRequested);
Assert.IsFalse(handled);
}
[TestMethod]
public void MustDetectZoomInCommand()
{
var zoomIn = false;
var zoomOut = false;
var zoomReset = false;
sut.ZoomInRequested += () => zoomIn = true;
sut.ZoomOutRequested += () => zoomOut = true;
sut.ZoomResetRequested += () => zoomReset = true;
var handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.Add, default(int), CefEventFlags.ControlDown, false);
Assert.IsFalse(handled);
Assert.IsTrue(zoomIn);
Assert.IsFalse(zoomOut);
Assert.IsFalse(zoomReset);
zoomIn = false;
handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.D1, default(int), CefEventFlags.ControlDown | CefEventFlags.ShiftDown, false);
Assert.IsFalse(handled);
Assert.IsTrue(zoomIn);
Assert.IsFalse(zoomOut);
Assert.IsFalse(zoomReset);
}
[TestMethod]
public void MustDetectZoomOutCommand()
{
var zoomIn = false;
var zoomOut = false;
var zoomReset = false;
sut.ZoomInRequested += () => zoomIn = true;
sut.ZoomOutRequested += () => zoomOut = true;
sut.ZoomResetRequested += () => zoomReset = true;
var handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.Subtract, default(int), CefEventFlags.ControlDown, false);
Assert.IsFalse(handled);
Assert.IsFalse(zoomIn);
Assert.IsTrue(zoomOut);
Assert.IsFalse(zoomReset);
zoomOut = false;
handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.OemMinus, default(int), CefEventFlags.ControlDown, false);
Assert.IsFalse(handled);
Assert.IsFalse(zoomIn);
Assert.IsTrue(zoomOut);
Assert.IsFalse(zoomReset);
}
[TestMethod]
public void MustDetectZoomResetCommand()
{
var zoomIn = false;
var zoomOut = false;
var zoomReset = false;
sut.ZoomInRequested += () => zoomIn = true;
sut.ZoomOutRequested += () => zoomOut = true;
sut.ZoomResetRequested += () => zoomReset = true;
var handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.D0, default(int), CefEventFlags.ControlDown, false);
Assert.IsFalse(handled);
Assert.IsFalse(zoomIn);
Assert.IsFalse(zoomOut);
Assert.IsTrue(zoomReset);
zoomReset = false;
handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.NumPad0, default(int), CefEventFlags.ControlDown, false);
Assert.IsFalse(handled);
Assert.IsFalse(zoomIn);
Assert.IsFalse(zoomOut);
Assert.IsTrue(zoomReset);
}
}
}

View file

@ -0,0 +1,313 @@
/*
* 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;
using SafeExamBrowser.Settings.Browser.Filter;
using SafeExamBrowser.Settings.Browser.Proxy;
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
using Request = SafeExamBrowser.Browser.Contracts.Filters.Request;
using ResourceHandler = SafeExamBrowser.Browser.Handlers.ResourceHandler;
namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
[TestClass]
public class RequestHandlerTests
{
private AppConfig appConfig;
private Mock<IRequestFilter> filter;
private Mock<IKeyGenerator> keyGenerator;
private Mock<ILogger> logger;
private BrowserSettings settings;
private WindowSettings windowSettings;
private ResourceHandler resourceHandler;
private Mock<IText> text;
private TestableRequestHandler sut;
[TestInitialize]
public void Initialize()
{
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, keyGenerator.Object, logger.Object, default, 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]
public void MustDetectQuitUrl()
{
var eventFired = false;
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;
var blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);
Assert.IsTrue(blocked);
Assert.IsTrue(eventFired);
blocked = false;
eventFired = false;
request.SetupGet(r => r.Url).Returns("http://www.bye.com");
blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);
Assert.IsFalse(blocked);
Assert.IsFalse(eventFired);
}
[TestMethod]
public void MustIgnoreTrailingSlashForQuitUrl()
{
var eventFired = false;
var quitUrl = "http://www.byebye.com";
var request = new Mock<IRequest>();
request.SetupGet(r => r.Url).Returns($"{quitUrl}/");
settings.QuitUrl = quitUrl;
sut.QuitUrlVisited += (url) => eventFired = true;
var blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);
Assert.IsTrue(blocked);
Assert.IsTrue(eventFired);
blocked = false;
eventFired = false;
request.SetupGet(r => r.Url).Returns(quitUrl);
settings.QuitUrl = $"{quitUrl}/";
blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);
Assert.IsTrue(blocked);
Assert.IsTrue(eventFired);
}
[TestMethod]
public void MustFilterMainRequests()
{
var eventFired = false;
var request = new Mock<IRequest>();
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);
settings.Filter.ProcessContentRequests = false;
settings.Filter.ProcessMainRequests = true;
sut.RequestBlocked += (u) => eventFired = true;
var blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);
filter.Verify(f => f.Process(It.Is<Request>(r => r.Url.Equals(url))), Times.Once);
Assert.IsTrue(blocked);
Assert.IsTrue(eventFired);
blocked = false;
eventFired = false;
request.SetupGet(r => r.ResourceType).Returns(ResourceType.SubFrame);
blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);
filter.Verify(f => f.Process(It.Is<Request>(r => r.Url.Equals(url))), Times.Once);
Assert.IsFalse(blocked);
Assert.IsFalse(eventFired);
}
[TestMethod]
public void MustFilterContentRequests()
{
var eventFired = false;
var request = new Mock<IRequest>();
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);
settings.Filter.ProcessContentRequests = true;
settings.Filter.ProcessMainRequests = false;
sut.RequestBlocked += (u) => eventFired = true;
var blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);
filter.Verify(f => f.Process(It.Is<Request>(r => r.Url.Equals(url))), Times.Once);
Assert.IsTrue(blocked);
Assert.IsFalse(eventFired);
blocked = false;
eventFired = false;
request.SetupGet(r => r.ResourceType).Returns(ResourceType.MainFrame);
blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);
filter.Verify(f => f.Process(It.Is<Request>(r => r.Url.Equals(url))), Times.Once);
Assert.IsFalse(blocked);
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()
{
var callback = new Mock<IAuthCallback>();
var proxy1 = new ProxyConfiguration { Host = "www.test.com", Username = "Sepp", Password = "1234", Port = 10, RequiresAuthentication = true };
var proxy2 = new ProxyConfiguration { Host = "www.nope.com", Username = "Peter", Password = "4321", Port = 10, RequiresAuthentication = false };
settings.Proxy.Proxies.Add(proxy1);
settings.Proxy.Proxies.Add(proxy2);
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);
callback.Verify(c => c.Continue(It.Is<string>(u => u.Equals(proxy2.Username)), It.Is<string>(p => p.Equals(proxy2.Password))), Times.Never);
Assert.IsTrue(result);
}
[TestMethod]
public void MustNotUseProxyCredentialsIfNoProxy()
{
var callback = new Mock<IAuthCallback>();
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);
}
private class TestableRequestHandler : RequestHandler
{
internal TestableRequestHandler(AppConfig appConfig, IRequestFilter filter, ILogger logger, ResourceHandler resourceHandler, BrowserSettings settings, WindowSettings windowSettings) : base(appConfig, filter, logger, resourceHandler, settings, windowSettings)
{
}
public new bool GetAuthCredentials(IWebBrowser webBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback)
{
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

@ -0,0 +1,363 @@
/*
* 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.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;
using Request = SafeExamBrowser.Browser.Contracts.Filters.Request;
using ResourceHandler = SafeExamBrowser.Browser.Handlers.ResourceHandler;
namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
[TestClass]
public class ResourceHandlerTests
{
private AppConfig appConfig;
private Mock<IRequestFilter> filter;
private Mock<IKeyGenerator> keyGenerator;
private Mock<ILogger> logger;
private BrowserSettings settings;
private WindowSettings windowSettings;
private Mock<IText> text;
private TestableResourceHandler sut;
[TestInitialize]
public void Initialize()
{
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, keyGenerator.Object, logger.Object, SessionMode.Server, settings, windowSettings, text.Object);
}
[TestMethod]
public void MustAppendCustomHeadersForSameDomain()
{
var browser = new Mock<IWebBrowser>();
var headers = default(NameValueCollection);
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.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 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.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>());
request.VerifyGet(r => r.Headers, Times.Never);
request.VerifySet(r => r.Headers = It.IsAny<NameValueCollection>(), Times.Never);
Assert.AreEqual(CefReturnValue.Continue, result);
Assert.IsNull(headers["X-SafeExamBrowser-ConfigKeyHash"]);
Assert.IsNull(headers["X-SafeExamBrowser-RequestHash"]);
}
[TestMethod]
public void MustBlockMailToUrls()
{
var request = new Mock<IRequest>();
var url = $"{Uri.UriSchemeMailto}:someone@somewhere.org";
request.SetupGet(r => r.Url).Returns(url);
var result = sut.OnBeforeResourceLoad(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, Mock.Of<IRequestCallback>());
Assert.AreEqual(CefReturnValue.Cancel, result);
}
[TestMethod]
public void MustFilterContentRequests()
{
var request = new Mock<IRequest>();
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);
request.SetupGet(r => r.Url).Returns(url);
settings.Filter.ProcessContentRequests = true;
settings.Filter.ProcessMainRequests = true;
var resourceHandler = sut.GetResourceHandler(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object);
filter.Verify(f => f.Process(It.Is<Request>(r => r.Url.Equals(url))), Times.Once);
Assert.IsNotNull(resourceHandler);
}
[TestMethod]
public void MustLetOperatingSystemHandleUnknownProtocols()
{
Assert.IsTrue(sut.OnProtocolExecution(default, default, default, default));
}
[TestMethod]
public void MustRedirectToDisablePdfToolbar()
{
var frame = new Mock<IFrame>();
var headers = new NameValueCollection { { "Content-Type", MediaTypeNames.Application.Pdf } };
var request = new Mock<IRequest>();
var response = new Mock<IResponse>();
var url = "http://www.host.org/some-document";
request.SetupGet(r => r.ResourceType).Returns(ResourceType.MainFrame);
request.SetupGet(r => r.Url).Returns(url);
response.SetupGet(r => r.Headers).Returns(headers);
settings.AllowPdfReader = true;
settings.AllowPdfReaderToolbar = false;
var result = sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), frame.Object, request.Object, response.Object);
frame.Verify(b => b.LoadUrl(It.Is<string>(s => s.Equals($"{url}#toolbar=0"))), Times.Once);
Assert.IsTrue(result);
frame.Reset();
request.SetupGet(r => r.Url).Returns($"{url}#toolbar=0");
result = sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), frame.Object, request.Object, response.Object);
frame.Verify(b => b.LoadUrl(It.IsAny<string>()), Times.Never);
Assert.IsFalse(result);
}
[TestMethod]
public void MustReplaceSebScheme()
{
var request = new Mock<IRequest>();
var url = default(string);
appConfig.SebUriScheme = "abc";
appConfig.SebUriSchemeSecure = "abcs";
request.SetupGet(r => r.Headers).Returns(new NameValueCollection());
request.SetupGet(r => r.Url).Returns($"{appConfig.SebUriScheme}://www.host.org");
request.SetupSet(r => r.Url = It.IsAny<string>()).Callback<string>(u => url = u);
var result = sut.OnBeforeResourceLoad(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, Mock.Of<IRequestCallback>());
Assert.AreEqual(CefReturnValue.Continue, result);
Assert.AreEqual("http://www.host.org/", url);
request.SetupGet(r => r.Url).Returns($"{appConfig.SebUriSchemeSecure}://www.host.org");
request.SetupSet(r => r.Url = It.IsAny<string>()).Callback<string>(u => url = u);
result = sut.OnBeforeResourceLoad(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, Mock.Of<IRequestCallback>());
Assert.AreEqual(CefReturnValue.Continue, result);
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, keyGenerator, logger, sessionMode, settings, windowSettings, text)
{
}
public new IResourceHandler GetResourceHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request)
{
return base.GetResourceHandler(webBrowser, browser, frame, request);
}
public new CefReturnValue OnBeforeResourceLoad(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback)
{
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

@ -0,0 +1,17 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("SafeExamBrowser.Browser.UnitTests")]
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Browser.UnitTests")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
[assembly: ComVisible(false)]
[assembly: Guid("f54c4c0e-4c72-4f88-a389-7f6de3ccb745")]
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0.0")]

View file

@ -0,0 +1,220 @@
<?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="..\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>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{F54C4C0E-4C72-4F88-A389-7F6DE3CCB745}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.Browser.UnitTests</RootNamespace>
<AssemblyName>SafeExamBrowser.Browser.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|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</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>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</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="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.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.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Filters\LegacyFilter.cs" />
<Compile Include="Filters\RequestFilterTests.cs" />
<Compile Include="Filters\RuleFactoryTests.cs" />
<Compile Include="Filters\Rules\RegexRuleTests.cs" />
<Compile Include="Filters\Rules\SimplifiedRuleTests.cs" />
<Compile Include="Handlers\ContextMenuHandlerTests.cs" />
<Compile Include="Handlers\DialogHandlerTests.cs" />
<Compile Include="Handlers\DisplayHandlerTests.cs" />
<Compile Include="Handlers\DownloadHandlerTests.cs" />
<Compile Include="Handlers\KeyboardHandlerTests.cs" />
<Compile Include="Handlers\RequestHandlerTests.cs" />
<Compile Include="Handlers\ResourceHandlerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config">
<SubType>Designer</SubType>
</None>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SafeExamBrowser.Browser.Contracts\SafeExamBrowser.Browser.Contracts.csproj">
<Project>{5fb5273d-277c-41dd-8593-a25ce1aff2e9}</Project>
<Name>SafeExamBrowser.Browser.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Browser\SafeExamBrowser.Browser.csproj">
<Project>{04e653f1-98e6-4e34-9dd7-7f2bc1a8b767}</Project>
<Name>SafeExamBrowser.Browser</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Configuration.Contracts\SafeExamBrowser.Configuration.Contracts.csproj">
<Project>{7d74555e-63e1-4c46-bd0a-8580552368c8}</Project>
<Name>SafeExamBrowser.Configuration.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.I18n.Contracts\SafeExamBrowser.I18n.Contracts.csproj">
<Project>{1858ddf3-bc2a-4bff-b663-4ce2ffeb8b7d}</Project>
<Name>SafeExamBrowser.I18n.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.Settings\SafeExamBrowser.Settings.csproj">
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
<Name>SafeExamBrowser.Settings</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.UserInterface.Contracts\SafeExamBrowser.UserInterface.Contracts.csproj">
<Project>{c7889e97-6ff6-4a58-b7cb-521ed276b316}</Project>
<Name>SafeExamBrowser.UserInterface.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\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\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

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
</dependentAssembly>
<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="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>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" /></startup></configuration>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<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

@ -0,0 +1,501 @@
/*
* 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.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using CefSharp;
using CefSharp.WinForms;
using SafeExamBrowser.Applications.Contracts.Events;
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;
using SafeExamBrowser.Settings.Browser.Proxy;
using SafeExamBrowser.Settings.Logging;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
using SafeExamBrowser.WindowsApi.Contracts;
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
namespace SafeExamBrowser.Browser
{
public class BrowserApplication : IBrowserApplication
{
private int windowIdCounter = default;
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; }
public Guid Id { get; private set; }
public string Name { get; private set; }
public string Tooltip { get; private set; }
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public event LoseFocusRequestedEventHandler LoseFocusRequested;
public event TerminationRequestedEventHandler TerminationRequested;
public event UserIdentifierDetectedEventHandler UserIdentifierDetected;
public event WindowsChangedEventHandler WindowsChanged;
public BrowserApplication(
AppConfig appConfig,
BrowserSettings settings,
IFileSystemDialog fileSystemDialog,
IHashAlgorithm hashAlgorithm,
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.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 void Focus(bool forward)
{
windows.ForEach(window =>
{
window.Focus(forward);
});
}
public IEnumerable<IBrowserWindow> GetWindows()
{
return new List<IBrowserWindow>(windows);
}
public void Initialize()
{
logger.Info("Starting initialization...");
var cefSettings = InitializeCefSettings();
var success = Cef.Initialize(cefSettings, true, default(IApp));
InitializeApplicationInfo();
if (success)
{
InitializeIntegrityKeys();
if (settings.DeleteCookiesOnStartup)
{
DeleteCookies();
}
if (settings.UseTemporaryDownAndUploadDirectory)
{
CreateTemporaryDownAndUploadDirectory();
}
logger.Info("Initialized browser.");
}
else
{
throw new Exception("Failed to initialize browser!");
}
}
public void Start()
{
CreateNewWindow();
}
public void Terminate()
{
logger.Info("Initiating termination...");
AwaitReady();
foreach (var window in windows)
{
window.Closed -= Window_Closed;
window.Close();
logger.Info($"Closed browser window #{window.Id}.");
}
if (settings.UseTemporaryDownAndUploadDirectory)
{
DeleteTemporaryDownAndUploadDirectory();
}
if (settings.DeleteCookiesOnShutdown)
{
DeleteCookies();
}
Cef.Shutdown();
logger.Info("Terminated browser.");
if (settings.DeleteCacheOnShutdown && settings.DeleteCookiesOnShutdown)
{
DeleteCache();
}
else
{
logger.Info("Retained browser cache.");
}
}
private void AwaitReady()
{
// 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).
Thread.Sleep(500);
}
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);
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
{
Directory.Delete(appConfig.BrowserCachePath, true);
logger.Info("Deleted browser cache.");
}
catch (Exception e)
{
logger.Error("Failed to delete browser cache!", e);
}
}
private void DeleteCookies()
{
var callback = new TaskDeleteCookiesCallback();
callback.Task.ContinueWith(task =>
{
if (!task.IsCompleted || task.Result == TaskDeleteCookiesCallback.InvalidNoOfCookiesDeleted)
{
logger.Warn("Failed to delete cookies!");
}
else
{
logger.Debug($"Deleted {task.Result} cookies.");
}
});
if (Cef.GetGlobalCookieManager().DeleteCookies(callback: callback))
{
logger.Debug("Successfully initiated cookie deletion.");
}
else
{
logger.Warn("Failed to initiate cookie deletion!");
}
}
private string GenerateStartUrl()
{
var url = settings.StartUrl;
if (settings.UseQueryParameter)
{
if (url.Contains("?") && settings.StartUrlQuery?.Length > 1 && Uri.TryCreate(url, UriKind.Absolute, out var uri))
{
url = url.Replace(uri.Query, $"{uri.Query}&{settings.StartUrlQuery.Substring(1)}");
}
else
{
url = $"{url}{settings.StartUrlQuery}";
}
}
return url;
}
private void InitializeApplicationInfo()
{
AutoStart = true;
Icon = new BrowserIconResource();
Id = Guid.NewGuid();
Name = text.Get(TextKey.Browser_Name);
Tooltip = text.Get(TextKey.Browser_Tooltip);
}
private CefSettings InitializeCefSettings()
{
var warning = logger.LogLevel == LogLevel.Warning;
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;
cefSettings.LogSeverity = error ? LogSeverity.Error : (warning ? LogSeverity.Warning : LogSeverity.Info);
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");
}
if (!settings.AllowSpellChecking)
{
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}");
logger.Debug($"Log Severity: {cefSettings.LogSeverity}.");
logger.Debug($"PDF Reader: {(settings.AllowPdfReader ? "Enabled" : "Disabled")}.");
logger.Debug($"Session Persistence: {(cefSettings.PersistSessionCookies ? "Enabled" : "Disabled")}.");
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)
{
if (settings.Proxy.AutoConfigure)
{
cefSettings.CefCommandLineArgs.Add("proxy-pac-url", settings.Proxy.AutoConfigureUrl);
}
if (settings.Proxy.AutoDetect)
{
cefSettings.CefCommandLineArgs.Add("proxy-auto-detect", "");
}
if (settings.Proxy.BypassList.Any())
{
cefSettings.CefCommandLineArgs.Add("proxy-bypass-list", string.Join(";", settings.Proxy.BypassList));
}
if (settings.Proxy.Proxies.Any())
{
var proxies = new List<string>();
foreach (var proxy in settings.Proxy.Proxies)
{
proxies.Add($"{ToScheme(proxy.Protocol)}={proxy.Host}:{proxy.Port}");
}
cefSettings.CefCommandLineArgs.Add("proxy-server", string.Join(";", proxies));
}
}
}
private string InitializeUserAgent()
{
var osVersion = $"{Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}";
var sebVersion = $"SEB/{appConfig.ProgramInformationalVersion}";
var userAgent = default(string);
if (settings.UseCustomUserAgent)
{
userAgent = $"{settings.CustomUserAgent} {sebVersion}";
}
else
{
userAgent = $"Mozilla/5.0 (Windows NT {osVersion}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{Cef.ChromiumVersion} {sebVersion}";
}
if (!string.IsNullOrWhiteSpace(settings.UserAgentSuffix))
{
userAgent = $"{userAgent} {settings.UserAgentSuffix}";
}
return userAgent;
}
private string ToScheme(ProxyProtocol protocol)
{
switch (protocol)
{
case ProxyProtocol.Ftp:
return Uri.UriSchemeFtp;
case ProxyProtocol.Http:
return Uri.UriSchemeHttp;
case ProxyProtocol.Https:
return Uri.UriSchemeHttps;
case ProxyProtocol.Socks:
return "socks";
}
throw new NotImplementedException($"Mapping for proxy protocol '{protocol}' is not yet implemented!");
}
private void Window_Closed(int id)
{
windows.Remove(windows.First(i => i.Id == id));
WindowsChanged?.Invoke();
logger.Info($"Window #{id} has been closed.");
}
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 window in windows)
{
window.Closed -= Window_Closed;
window.Close();
logger.Info($"Closed browser window #{window.Id}.");
}
windows.Clear();
WindowsChanged?.Invoke();
if (settings.DeleteCookiesOnStartup && settings.DeleteCookiesOnShutdown)
{
DeleteCookies();
}
nativeMethods.EmptyClipboard();
CreateNewWindow();
logger.Info("Successfully reset browser.");
}
}
}

View file

@ -1,188 +0,0 @@
/*
* Copyright (c) 2019 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 System;
using System.Collections.Generic;
using System.Linq;
using CefSharp;
using CefSharp.WinForms;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Contracts.Applications;
using SafeExamBrowser.Contracts.Browser;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
using SafeExamBrowser.Contracts.UserInterface.Shell;
using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.BrowserSettings;
namespace SafeExamBrowser.Browser
{
public class BrowserApplicationController : IBrowserApplicationController
{
private int instanceIdCounter = default(int);
private AppConfig appConfig;
private IList<IApplicationControl> controls;
private IList<IApplicationInstance> instances;
private IMessageBox messageBox;
private IModuleLogger logger;
private BrowserSettings settings;
private IText text;
private IUserInterfaceFactory uiFactory;
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public BrowserApplicationController(
AppConfig appConfig,
BrowserSettings settings,
IMessageBox messageBox,
IModuleLogger logger,
IText text,
IUserInterfaceFactory uiFactory)
{
this.appConfig = appConfig;
this.controls = new List<IApplicationControl>();
this.instances = new List<IApplicationInstance>();
this.logger = logger;
this.messageBox = messageBox;
this.settings = settings;
this.text = text;
this.uiFactory = uiFactory;
}
public void Initialize()
{
var cefSettings = InitializeCefSettings();
var success = Cef.Initialize(cefSettings, true, default(IApp));
logger.Info("Initialized browser.");
if (!success)
{
throw new Exception("Failed to initialize browser!");
}
}
public void RegisterApplicationControl(IApplicationControl control)
{
control.Clicked += ApplicationControl_Clicked;
controls.Add(control);
}
public void Start()
{
CreateNewInstance();
}
public void Terminate()
{
foreach (var instance in instances)
{
instance.Terminated -= Instance_Terminated;
instance.Window.Close();
logger.Info($"Terminated browser instance {instance.Id}.");
}
Cef.Shutdown();
logger.Info("Terminated browser.");
}
private void CreateNewInstance(string url = null)
{
var id = new BrowserInstanceIdentifier(++instanceIdCounter);
var isMainInstance = instances.Count == 0;
var instanceLogger = logger.CloneFor($"BrowserInstance {id}");
var startUrl = url ?? settings.StartUrl;
var instance = new BrowserApplicationInstance(appConfig, settings, id, isMainInstance, messageBox, instanceLogger, text, uiFactory, startUrl);
instance.Initialize();
instance.ConfigurationDownloadRequested += (fileName, args) => ConfigurationDownloadRequested?.Invoke(fileName, args);
instance.PopupRequested += Instance_PopupRequested;
instance.Terminated += Instance_Terminated;
foreach (var control in controls)
{
control.RegisterInstance(instance);
}
instances.Add(instance);
instance.Window.Show();
logger.Info($"Created browser instance {instance.Id}.");
}
private CefSettings InitializeCefSettings()
{
var warning = logger.LogLevel == LogLevel.Warning;
var error = logger.LogLevel == LogLevel.Error;
var cefSettings = new CefSettings
{
CachePath = appConfig.BrowserCachePath,
LogFile = appConfig.BrowserLogFile,
LogSeverity = error ? LogSeverity.Error : (warning ? LogSeverity.Warning : LogSeverity.Info),
UserAgent = InitializeUserAgent()
};
cefSettings.CefCommandLineArgs.Add("touch-events", "enabled");
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}");
logger.Debug($"Log severity: {cefSettings.LogSeverity}");
return cefSettings;
}
private void ApplicationControl_Clicked(InstanceIdentifier id = null)
{
if (id == null)
{
CreateNewInstance();
}
else
{
instances.FirstOrDefault(i => i.Id == id)?.Window?.BringToForeground();
}
}
private void Instance_PopupRequested(PopupRequestedEventArgs args)
{
logger.Info($"Received request to create new instance for '{args.Url}'...");
CreateNewInstance(args.Url);
}
private void Instance_Terminated(InstanceIdentifier id)
{
instances.Remove(instances.FirstOrDefault(i => i.Id == id));
logger.Info($"Browser instance {id} was terminated.");
}
/// <summary>
/// TODO: Workaround to correctly set the user agent due to missing support for request interception for requests made by service workers.
/// Remove once CEF fully supports service workers and reactivate the functionality in <see cref="Handlers.RequestHandler"/>!
/// </summary>
private string InitializeUserAgent()
{
var osVersion = $"{Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}";
var sebVersion = $"SEB/{appConfig.ProgramVersion}";
if (settings.UseCustomUserAgent)
{
return $"{settings.CustomUserAgent} {sebVersion}";
}
else
{
return $"Mozilla/5.0 (Windows NT {osVersion}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{Cef.ChromiumVersion} {sebVersion}";
}
}
}
}

View file

@ -1,20 +0,0 @@
/*
* Copyright (c) 2019 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 SafeExamBrowser.Contracts.Applications;
using SafeExamBrowser.Contracts.Core;
namespace SafeExamBrowser.Browser
{
public class BrowserApplicationInfo : IApplicationInfo
{
public string Name => "Safe Exam Browser";
public string Tooltip => Name;
public IIconResource IconResource { get; } = new BrowserIconResource();
}
}

View file

@ -1,289 +0,0 @@
/*
* Copyright (c) 2019 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 System;
using System.Net.Http;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.Contracts.Applications;
using SafeExamBrowser.Contracts.Applications.Events;
using SafeExamBrowser.Contracts.Browser;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.Browser;
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
using SafeExamBrowser.Contracts.UserInterface.Windows;
namespace SafeExamBrowser.Browser
{
internal class BrowserApplicationInstance : IApplicationInstance
{
private const double ZOOM_FACTOR = 0.2;
private AppConfig appConfig;
private IBrowserControl control;
private IBrowserWindow window;
private HttpClient httpClient;
private bool isMainInstance;
private IMessageBox messageBox;
private IModuleLogger logger;
private BrowserSettings settings;
private IText text;
private IUserInterfaceFactory uiFactory;
private string url;
private double zoomLevel;
private BrowserWindowSettings WindowSettings
{
get { return isMainInstance ? settings.MainWindowSettings : settings.AdditionalWindowSettings; }
}
public InstanceIdentifier Id { get; private set; }
public string Name { get; private set; }
public IWindow Window { get { return window; } }
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public event IconChangedEventHandler IconChanged;
public event InstanceTerminatedEventHandler Terminated;
public event NameChangedEventHandler NameChanged;
public event PopupRequestedEventHandler PopupRequested;
public BrowserApplicationInstance(
AppConfig appConfig,
BrowserSettings settings,
InstanceIdentifier id,
bool isMainInstance,
IMessageBox messageBox,
IModuleLogger logger,
IText text,
IUserInterfaceFactory uiFactory,
string url)
{
this.appConfig = appConfig;
this.Id = id;
this.httpClient = new HttpClient();
this.isMainInstance = isMainInstance;
this.messageBox = messageBox;
this.logger = logger;
this.settings = settings;
this.text = text;
this.uiFactory = uiFactory;
this.url = url;
}
internal void Initialize()
{
var contextMenuHandler = new ContextMenuHandler();
var displayHandler = new DisplayHandler();
var downloadLogger = logger.CloneFor($"{nameof(DownloadHandler)} {Id}");
var downloadHandler = new DownloadHandler(appConfig, settings, downloadLogger);
var keyboardHandler = new KeyboardHandler();
var lifeSpanHandler = new LifeSpanHandler();
var requestHandler = new RequestHandler(appConfig);
displayHandler.FaviconChanged += DisplayHandler_FaviconChanged;
displayHandler.ProgressChanged += DisplayHandler_ProgressChanged;
downloadHandler.ConfigurationDownloadRequested += DownloadHandler_ConfigurationDownloadRequested;
keyboardHandler.ReloadRequested += ReloadRequested;
keyboardHandler.ZoomInRequested += ZoomInRequested;
keyboardHandler.ZoomOutRequested += ZoomOutRequested;
keyboardHandler.ZoomResetRequested += ZoomResetRequested;
lifeSpanHandler.PopupRequested += LifeSpanHandler_PopupRequested;
control = new BrowserControl(contextMenuHandler, displayHandler, downloadHandler, keyboardHandler, lifeSpanHandler, requestHandler, url);
control.AddressChanged += Control_AddressChanged;
control.LoadingStateChanged += Control_LoadingStateChanged;
control.TitleChanged += Control_TitleChanged;
control.Initialize();
logger.Debug("Initialized browser control.");
window = uiFactory.CreateBrowserWindow(control, settings, isMainInstance);
window.Closing += () => Terminated?.Invoke(Id);
window.AddressChanged += Window_AddressChanged;
window.BackwardNavigationRequested += Window_BackwardNavigationRequested;
window.DeveloperConsoleRequested += Window_DeveloperConsoleRequested;
window.ForwardNavigationRequested += Window_ForwardNavigationRequested;
window.ReloadRequested += ReloadRequested;
window.ZoomInRequested += ZoomInRequested;
window.ZoomOutRequested += ZoomOutRequested;
window.ZoomResetRequested += ZoomResetRequested;
window.UpdateZoomLevel(CalculateZoomPercentage());
logger.Debug("Initialized browser window.");
}
private void Control_AddressChanged(string address)
{
logger.Debug($"Navigated to '{address}'.");
window.UpdateAddress(address);
}
private void Control_LoadingStateChanged(bool isLoading)
{
window.CanNavigateBackwards = WindowSettings.AllowBackwardNavigation && control.CanNavigateBackwards;
window.CanNavigateForwards = WindowSettings.AllowForwardNavigation && control.CanNavigateForwards;
window.UpdateLoadingState(isLoading);
}
private void Control_TitleChanged(string title)
{
window.UpdateTitle(title);
NameChanged?.Invoke(title);
}
private void DisplayHandler_FaviconChanged(string uri)
{
var request = new HttpRequestMessage(HttpMethod.Head, uri);
var response = httpClient.SendAsync(request).ContinueWith(task =>
{
if (task.IsCompleted && task.Result.IsSuccessStatusCode)
{
var icon = new BrowserIconResource(uri);
IconChanged?.Invoke(icon);
window.UpdateIcon(icon);
}
});
}
private void DisplayHandler_ProgressChanged(double value)
{
window.UpdateProgress(value);
}
private void DownloadHandler_ConfigurationDownloadRequested(string fileName, DownloadEventArgs args)
{
if (settings.AllowConfigurationDownloads)
{
args.BrowserWindow = window;
logger.Debug($"Forwarding download request for configuration file '{fileName}'.");
ConfigurationDownloadRequested?.Invoke(fileName, args);
}
else
{
logger.Debug($"Discarded download request for configuration file '{fileName}'.");
}
}
private void LifeSpanHandler_PopupRequested(PopupRequestedEventArgs args)
{
if (settings.AllowPopups)
{
logger.Debug($"Forwarding request to open new window for '{args.Url}'...");
PopupRequested?.Invoke(args);
}
else
{
logger.Debug($"Blocked attempt to open new window for '{args.Url}'.");
}
}
private void ReloadRequested()
{
if (WindowSettings.AllowReloading && WindowSettings.ShowReloadWarning)
{
var result = messageBox.Show(TextKey.MessageBox_ReloadConfirmation, TextKey.MessageBox_ReloadConfirmationTitle, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
if (result == MessageBoxResult.Yes)
{
logger.Debug("The user confirmed reloading the current page...");
control.Reload();
}
else
{
logger.Debug("The user aborted reloading the current page.");
}
}
else if (WindowSettings.AllowReloading)
{
logger.Debug("Reloading current page...");
control.Reload();
}
else
{
logger.Debug("Blocked reload attempt, as the user is not allowed to reload web pages.");
}
}
private void Window_AddressChanged(string address)
{
var isValid = Uri.TryCreate(address, UriKind.Absolute, out _) || Uri.TryCreate($"https://{address}", UriKind.Absolute, out _);
if (isValid)
{
logger.Debug($"The user requested to navigate to '{address}', the URI is valid.");
control.NavigateTo(address);
}
else
{
logger.Debug($"The user requested to navigate to '{address}', but the URI is not valid.");
window.UpdateAddress(string.Empty);
}
}
private void Window_BackwardNavigationRequested()
{
logger.Debug("Navigating backwards...");
control.NavigateBackwards();
}
private void Window_DeveloperConsoleRequested()
{
logger.Debug("Showing developer console...");
control.ShowDeveloperConsole();
}
private void Window_ForwardNavigationRequested()
{
logger.Debug("Navigating forwards...");
control.NavigateForwards();
}
private void ZoomInRequested()
{
if (settings.AllowPageZoom && CalculateZoomPercentage() < 300)
{
zoomLevel += ZOOM_FACTOR;
control.Zoom(zoomLevel);
window.UpdateZoomLevel(CalculateZoomPercentage());
logger.Debug($"Increased page zoom to {CalculateZoomPercentage()}%.");
}
}
private void ZoomOutRequested()
{
if (settings.AllowPageZoom && CalculateZoomPercentage() > 25)
{
zoomLevel -= ZOOM_FACTOR;
control.Zoom(zoomLevel);
window.UpdateZoomLevel(CalculateZoomPercentage());
logger.Debug($"Decreased page zoom to {CalculateZoomPercentage()}%.");
}
}
private void ZoomResetRequested()
{
if (settings.AllowPageZoom)
{
zoomLevel = 0;
control.Zoom(0);
window.UpdateZoomLevel(CalculateZoomPercentage());
logger.Debug($"Reset page zoom to {CalculateZoomPercentage()}%.");
}
}
private double CalculateZoomPercentage()
{
return (zoomLevel * 25.0) + 100.0;
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 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,121 +7,189 @@
*/
using System;
using System.Linq;
using System.Threading.Tasks;
using CefSharp;
using CefSharp.WinForms;
using SafeExamBrowser.Contracts.UserInterface.Browser;
using SafeExamBrowser.Contracts.UserInterface.Browser.Events;
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 const uint WS_EX_NOACTIVATE = 0x08000000;
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 IContextMenuHandler contextMenuHandler;
private IDisplayHandler displayHandler;
private IDownloadHandler downloadHandler;
private IKeyboardHandler keyboardHandler;
private ILifeSpanHandler lifeSpanHandler;
private IRequestHandler requestHandler;
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;
private AddressChangedEventHandler addressChanged;
private LoadingStateChangedEventHandler loadingStateChanged;
private TitleChangedEventHandler titleChanged;
public bool CanNavigateBackwards => GetBrowser().CanGoBack;
public bool CanNavigateForwards => GetBrowser().CanGoForward;
event AddressChangedEventHandler IBrowserControl.AddressChanged
{
add { addressChanged += value; }
remove { addressChanged -= 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 (!control.IsDisposed)
{
control.Dispose(true);
}
}
public void ExecuteJavaScript(string code, Action<JavaScriptResult> callback = default)
{
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)
{
control.Find(term, forward, caseSensitive, !isInitial);
}
public void Initialize()
{
AddressChanged += (o, args) => addressChanged?.Invoke(args.Address);
LoadingStateChanged += (o, args) => loadingStateChanged?.Invoke(args.IsLoading);
TitleChanged += (o, args) => titleChanged?.Invoke(args.Title);
clipboard.Changed += Clipboard_Changed;
DisplayHandler = displayHandler;
DownloadHandler = downloadHandler;
KeyboardHandler = keyboardHandler;
LifeSpanHandler = lifeSpanHandler;
MenuHandler = contextMenuHandler;
RequestHandler = requestHandler;
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);
}
/// <summary>
/// TODO: This is a workaround due to the broken initial touch activation in version 73.1.130, it must be removed once fixed in CefSharp.
/// See https://github.com/cefsharp/CefSharp/issues/2776 for more information.
/// </summary>
protected override IWindowInfo CreateBrowserWindowInfo(IntPtr handle)
private void Clipboard_Changed(long id)
{
var windowInfo = base.CreateBrowserWindowInfo(handle);
ExecuteJavaScript($"SafeExamBrowser.clipboard.update({id}, '{clipboard.Content}');");
}
windowInfo.ExStyle &= ~WS_EX_NOACTIVATE;
private void Control_IsBrowserInitializedChanged(object sender, EventArgs e)
{
if (control.IsBrowserInitialized)
{
control.BrowserCore.GetHost().SetFocus(true);
}
}
return windowInfo;
private void WebBrowser_JavascriptMessageReceived(object sender, JavascriptMessageReceivedEventArgs e)
{
clipboard.Process(e);
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 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,16 +7,12 @@
*/
using System;
using SafeExamBrowser.Contracts.Core;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
namespace SafeExamBrowser.Browser
{
public class BrowserIconResource : IIconResource
public class BrowserIconResource : BitmapIconResource
{
public Uri Uri { get; private set; }
public bool IsBitmapResource => true;
public bool IsXamlResource => false;
public BrowserIconResource(string uri = null)
{
Uri = new Uri(uri ?? "pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/SafeExamBrowser.ico");

View file

@ -1,42 +0,0 @@
/*
* Copyright (c) 2019 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 SafeExamBrowser.Contracts.Applications;
namespace SafeExamBrowser.Browser
{
internal class BrowserInstanceIdentifier : InstanceIdentifier
{
public int Value { get; private set; }
public BrowserInstanceIdentifier(int id)
{
Value = id;
}
public override bool Equals(object other)
{
if (other is BrowserInstanceIdentifier id)
{
return Value == id.Value;
}
return false;
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
public override string ToString()
{
return $"#{Value}";
}
}
}

View file

@ -0,0 +1,788 @@
/*
* 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.Net.Http;
using System.Threading.Tasks;
using CefSharp;
using CefSharp.WinForms.Handler;
using CefSharp.WinForms.Host;
using SafeExamBrowser.Applications.Contracts.Events;
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;
using SafeExamBrowser.UserInterface.Contracts.Browser;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
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 BrowserWindow : Contracts.IBrowserWindow
{
private const string CLEAR_FIND_TERM = "thisisahacktoclearthesearchresultsasitappearsthatthereisnosuchfunctionalityincef";
private const double ZOOM_FACTOR = 0.2;
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 double zoomLevel;
private WindowSettings WindowSettings
{
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 TerminationRequestedEventHandler TerminationRequested;
internal event UserIdentifierDetectedEventHandler UserIdentifierDetected;
public event IconChangedEventHandler IconChanged;
public event TitleChangedEventHandler TitleChanged;
public BrowserWindow(
AppConfig appConfig,
Clipboard clipboard,
IFileSystemDialog fileSystemDialog,
IHashAlgorithm hashAlgorithm,
int id,
bool isMainWindow,
IKeyGenerator keyGenerator,
IModuleLogger logger,
IMessageBox messageBox,
SessionMode sessionMode,
BrowserSettings settings,
string startUrl,
IText text,
IUserInterfaceFactory uiFactory)
{
this.appConfig = appConfig;
this.clipboard = clipboard;
this.fileSystemDialog = fileSystemDialog;
this.hashAlgorithm = hashAlgorithm;
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;
}
public void Activate()
{
window.BringToForeground();
}
internal void Close()
{
window.Close();
Control.Destroy();
}
internal void Focus(bool forward)
{
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 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, 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;
requestHandler.QuitUrlVisited += RequestHandler_QuitUrlVisited;
requestHandler.RequestBlocked += RequestHandler_RequestBlocked;
resourceHandler.UserIdentifierDetected += (id) => UserIdentifierDetected?.Invoke(id);
InitializeRequestFilter(requestFilter);
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();
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)
{
var factory = new RuleFactory();
foreach (var settings in settings.Filter.Rules)
{
var rule = factory.CreateRule(settings.Type);
rule.Initialize(settings);
requestFilter.Load(rule);
}
logger.Debug($"Initialized request filter with {settings.Filter.Rules.Count} rule(s).");
if (requestFilter.Process(new Request { Url = settings.StartUrl }) != FilterResult.Allow)
{
var rule = factory.CreateRule(FilterRuleType.Simplified);
rule.Initialize(new FilterRuleSettings { Expression = settings.StartUrl, Result = FilterResult.Allow });
requestFilter.Load(rule);
logger.Debug($"Automatically created filter rule to allow start URL{(WindowSettings.UrlPolicy.CanLog() ? $" '{settings.StartUrl}'" : "")}.");
}
}
}
private void Control_AddressChanged(string address)
{
logger.Info($"Navigated{(WindowSettings.UrlPolicy.CanLog() ? $" to '{address}'" : "")}.");
Url = address;
window.UpdateAddress(address);
if (WindowSettings.UrlPolicy == UrlPolicy.Always || WindowSettings.UrlPolicy == UrlPolicy.BeforeTitle)
{
Title = address;
window.UpdateTitle(address);
TitleChanged?.Invoke(address);
}
AutoFind();
}
private void Control_LoadFailed(int errorCode, string errorText, bool isMainRequest, string url)
{
switch (errorCode)
{
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;
}
}
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 : "") + $" {requestInfo}";
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.UpdateLoadingState(isLoading);
}
private void Control_TitleChanged(string title)
{
if (WindowSettings.UrlPolicy != UrlPolicy.Always)
{
Title = title;
window.UpdateTitle(Title);
TitleChanged?.Invoke(Title);
}
}
private void DialogHandler_DialogRequested(DialogRequestedEventArgs args)
{
var isDownload = args.Operation == FileSystemOperation.Save;
var isUpload = args.Operation == FileSystemOperation.Open;
var isAllowed = (isDownload && settings.AllowDownloads) || (isUpload && settings.AllowUploads);
var initialPath = default(string);
if (isDownload)
{
initialPath = args.InitialPath;
}
else if (string.IsNullOrEmpty(settings.DownAndUploadDirectory))
{
initialPath = KnownFolders.Downloads.ExpandedPath;
}
else
{
initialPath = Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory);
}
if (isAllowed)
{
var result = fileSystemDialog.Show(
args.Element,
args.Operation,
initialPath,
title: args.Title,
parent: window,
restrictNavigation: !settings.AllowCustomDownAndUploadLocation,
showElementPath: settings.ShowFileSystemElementPath);
if (result.Success)
{
args.FullPath = result.FullPath;
args.Success = result.Success;
logger.Debug($"User selected path '{result.FullPath}' when asked to {args.Operation}->{args.Element}.");
}
else
{
logger.Debug($"User aborted file system dialog to {args.Operation}->{args.Element}.");
}
}
else
{
logger.Info($"Blocked file system dialog to {args.Operation}->{args.Element}, as {(isDownload ? "downloading" : "uploading")} is not allowed.");
ShowDownUploadNotAllowedMessage(isDownload);
}
}
private void DisplayHandler_FaviconChanged(string uri)
{
Task.Run(() =>
{
var request = new HttpRequestMessage(HttpMethod.Head, uri);
var response = httpClient.SendAsync(request).ContinueWith(task =>
{
if (task.IsCompleted && task.Result.IsSuccessStatusCode)
{
Icon = new BrowserIconResource(uri);
IconChanged?.Invoke(Icon);
window.UpdateIcon(Icon);
}
});
});
}
private void DisplayHandler_ProgressChanged(double value)
{
window.UpdateProgress(value);
}
private void DownloadHandler_ConfigurationDownloadRequested(string fileName, DownloadEventArgs args)
{
if (settings.AllowConfigurationDownloads)
{
logger.Debug($"Forwarding download request for configuration file '{fileName}'.");
ConfigurationDownloadRequested?.Invoke(fileName, args);
if (args.AllowDownload)
{
logger.Debug($"Download request for configuration file '{fileName}' was granted.");
}
else
{
logger.Debug($"Download request for configuration file '{fileName}' was denied.");
messageBox.Show(TextKey.MessageBox_ReconfigurationDenied, TextKey.MessageBox_ReconfigurationDeniedTitle, parent: window);
}
}
else
{
logger.Debug($"Discarded download request for configuration file '{fileName}'.");
}
}
private void DownloadHandler_DownloadAborted()
{
ShowDownUploadNotAllowedMessage();
}
private void DownloadHandler_DownloadUpdated(DownloadItemState state)
{
window.UpdateDownloadState(state);
}
private void HomeNavigationRequested()
{
if (IsMainWindow && (settings.UseStartUrlAsHomeUrl || !string.IsNullOrWhiteSpace(settings.HomeUrl)))
{
var navigate = false;
var url = settings.UseStartUrlAsHomeUrl ? settings.StartUrl : settings.HomeUrl;
if (settings.HomeNavigationRequiresPassword && !string.IsNullOrWhiteSpace(settings.HomePasswordHash))
{
var message = text.Get(TextKey.PasswordDialog_BrowserHomePasswordRequired);
var title = !string.IsNullOrWhiteSpace(settings.HomeNavigationMessage) ? settings.HomeNavigationMessage : text.Get(TextKey.PasswordDialog_BrowserHomePasswordRequiredTitle);
var dialog = uiFactory.CreatePasswordDialog(message, title);
var result = dialog.Show(window);
if (result.Success)
{
var passwordHash = hashAlgorithm.GenerateHashFor(result.Password);
if (settings.HomePasswordHash.Equals(passwordHash, StringComparison.OrdinalIgnoreCase))
{
navigate = true;
}
else
{
messageBox.Show(TextKey.MessageBox_InvalidHomePassword, TextKey.MessageBox_InvalidHomePasswordTitle, icon: MessageBoxIcon.Warning, parent: window);
}
}
}
else
{
var message = text.Get(TextKey.MessageBox_BrowserHomeQuestion);
var title = !string.IsNullOrWhiteSpace(settings.HomeNavigationMessage) ? settings.HomeNavigationMessage : text.Get(TextKey.MessageBox_BrowserHomeQuestionTitle);
var result = messageBox.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
navigate = result == MessageBoxResult.Yes;
}
if (navigate)
{
Control.NavigateTo(url);
}
}
}
private void KeyboardHandler_FindRequested()
{
if (settings.AllowFind)
{
window.ShowFindbar();
}
}
private void KeyboardHandler_FocusAddressBarRequested()
{
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 '{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() ? $" '{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 '{targetUrl}'" : "")} as it targets a different host.");
break;
default:
logger.Info($"Blocked request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{targetUrl}'" : "")}.");
break;
}
return creation;
}
private void RequestHandler_QuitUrlVisited(string url)
{
Task.Run(() =>
{
if (settings.ResetOnQuitUrl)
{
logger.Info("Forwarding request to reset browser...");
ResetRequested?.Invoke();
}
else
{
if (settings.ConfirmQuitUrl)
{
var message = text.Get(TextKey.MessageBox_BrowserQuitUrlConfirmation);
var title = text.Get(TextKey.MessageBox_BrowserQuitUrlConfirmationTitle);
var result = messageBox.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
var terminate = result == MessageBoxResult.Yes;
if (terminate)
{
logger.Info($"User confirmed termination via quit URL{(WindowSettings.UrlPolicy.CanLog() ? $" '{url}'" : "")}, forwarding request...");
TerminationRequested?.Invoke();
}
else
{
logger.Info($"User aborted termination via quit URL{(WindowSettings.UrlPolicy.CanLog() ? $" '{url}'" : "")}.");
}
}
else
{
logger.Info($"Automatically requesting termination due to quit URL{(WindowSettings.UrlPolicy.CanLog() ? $" '{url}'" : "")}...");
TerminationRequested?.Invoke();
}
}
});
}
private void RequestHandler_RequestBlocked(string url)
{
Task.Run(() =>
{
var message = text.Get(TextKey.MessageBox_BrowserNavigationBlocked).Replace("%%URL%%", WindowSettings.UrlPolicy.CanLogError() ? url : "");
var title = text.Get(TextKey.MessageBox_BrowserNavigationBlockedTitle);
Control.TitleChanged -= Control_TitleChanged;
if (url.Equals(startUrl, StringComparison.OrdinalIgnoreCase))
{
window.UpdateTitle($"*** {title} ***");
TitleChanged?.Invoke($"*** {title} ***");
}
messageBox.Show(message, title, parent: window);
Control.TitleChanged += Control_TitleChanged;
});
}
private void ReloadRequested()
{
if (WindowSettings.AllowReloading && WindowSettings.ShowReloadWarning)
{
var result = messageBox.Show(TextKey.MessageBox_ReloadConfirmation, TextKey.MessageBox_ReloadConfirmationTitle, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
if (result == MessageBoxResult.Yes)
{
logger.Debug("The user confirmed reloading the current page...");
Control.Reload();
}
else
{
logger.Debug("The user aborted reloading the current page.");
}
}
else if (WindowSettings.AllowReloading)
{
logger.Debug("Reloading current page...");
Control.Reload();
}
else
{
logger.Debug("Blocked reload attempt, as the user is not allowed to reload web pages.");
}
}
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 _);
if (isValid)
{
logger.Debug($"The user requested to navigate to '{address}', the URI is valid.");
Control.NavigateTo(address);
}
else
{
logger.Debug($"The user requested to navigate to '{address}', but the URI is not valid.");
window.UpdateAddress(string.Empty);
}
}
private void Window_BackwardNavigationRequested()
{
logger.Debug("Navigating backwards...");
Control.NavigateBackwards();
}
private void Window_Closing()
{
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();
}
private void Window_FindRequested(string term, bool isInitial, bool caseSensitive, bool forward = true)
{
if (settings.AllowFind)
{
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();
}
private void Window_LoseFocusRequested(bool forward)
{
LoseFocusRequested?.Invoke(forward);
}
private void ZoomInRequested()
{
if (settings.AllowPageZoom && CalculateZoomPercentage() < 300)
{
zoomLevel += ZOOM_FACTOR;
Control.Zoom(zoomLevel);
window.UpdateZoomLevel(CalculateZoomPercentage());
logger.Debug($"Increased page zoom to {CalculateZoomPercentage()}%.");
}
}
private void ZoomOutRequested()
{
if (settings.AllowPageZoom && CalculateZoomPercentage() > 25)
{
zoomLevel -= ZOOM_FACTOR;
Control.Zoom(zoomLevel);
window.UpdateZoomLevel(CalculateZoomPercentage());
logger.Debug($"Decreased page zoom to {CalculateZoomPercentage()}%.");
}
}
private void ZoomResetRequested()
{
if (settings.AllowPageZoom)
{
zoomLevel = 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,3 @@
<div style="background-color: lightgray; display: table; font-family: 'Segoe UI'; height: 100%; text-align: center; width: 100%">
<p style="display: table-cell; font-weight: bold; vertical-align: middle">%%MESSAGE%%</p>
</div>

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html style="height: 100%; width: 100%">
<head>
<meta charset="utf-8" />
<title>%%TITLE%%</title>
</head>
<body style="background-color: lightgray; display: table; font-family: 'Segoe UI'; height: 98%; text-align: center; width: 99%">
<div style="display: table-cell; vertical-align: middle">
<p style="font-weight: bold">%%MESSAGE%%</p>
<button onclick="window.history.back()" style="cursor: pointer">&#x2B60; %%BACK_BUTTON%%</button>
</div>
</body>
</html>

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

@ -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 ClipboardChangedEventHandler(long id);
}

View file

@ -0,0 +1,22 @@
/*
* 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.UserInterface.Contracts.FileSystemDialog;
namespace SafeExamBrowser.Browser.Events
{
internal class DialogRequestedEventArgs
{
internal FileSystemElement Element { get; set; }
internal string InitialPath { get; set; }
internal FileSystemOperation Operation { get; set; }
internal string FullPath { get; set; }
internal bool Success { get; set; }
internal string Title { get; set; }
}
}

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 DialogRequestedEventHandler(DialogRequestedEventArgs args);
}

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

@ -0,0 +1,14 @@
/*
* 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.UserInterface.Contracts.Browser.Data;
namespace SafeExamBrowser.Browser.Events
{
internal delegate void DownloadUpdatedEventHandler(DownloadItemState state);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 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) 2019 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) 2019 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 ProgressChangedEventHandler(double value);
}

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 ResetRequestedEventHandler();
}

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 UrlEventHandler(string url);
}

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

@ -0,0 +1,66 @@
/*
* 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 SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Settings.Browser.Filter;
namespace SafeExamBrowser.Browser.Filters
{
internal class RequestFilter : IRequestFilter
{
private IList<IRule> allowRules;
private IList<IRule> blockRules;
public FilterResult Default { get; set; }
internal RequestFilter()
{
allowRules = new List<IRule>();
blockRules = new List<IRule>();
Default = FilterResult.Block;
}
public void Load(IRule rule)
{
switch (rule.Result)
{
case FilterResult.Allow:
allowRules.Add(rule);
break;
case FilterResult.Block:
blockRules.Add(rule);
break;
default:
throw new NotImplementedException($"Filter processing for result '{rule.Result}' is not yet implemented!");
}
}
public FilterResult Process(Request request)
{
foreach (var rule in blockRules)
{
if (rule.IsMatch(request))
{
return FilterResult.Block;
}
}
foreach (var rule in allowRules)
{
if (rule.IsMatch(request))
{
return FilterResult.Allow;
}
}
return Default;
}
}
}

View file

@ -0,0 +1,31 @@
/*
* 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 SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Filters.Rules;
using SafeExamBrowser.Settings.Browser.Filter;
namespace SafeExamBrowser.Browser.Filters
{
internal class RuleFactory : IRuleFactory
{
public IRule CreateRule(FilterRuleType type)
{
switch (type)
{
case FilterRuleType.Regex:
return new RegexRule();
case FilterRuleType.Simplified:
return new SimplifiedRule();
default:
throw new NotImplementedException($"Filter rule of type '{type}' is not yet implemented!");
}
}
}
}

View file

@ -0,0 +1,52 @@
/*
* 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.Text.RegularExpressions;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Settings.Browser.Filter;
namespace SafeExamBrowser.Browser.Filters.Rules
{
internal class RegexRule : IRule
{
private string expression;
public FilterResult Result { get; private set; }
public void Initialize(FilterRuleSettings settings)
{
ValidateExpression(settings.Expression);
expression = settings.Expression;
Result = settings.Result;
}
public bool IsMatch(Request request)
{
return Regex.IsMatch(request.Url, expression, RegexOptions.IgnoreCase);
}
private void ValidateExpression(string expression)
{
if (expression == default(string))
{
throw new ArgumentNullException(nameof(expression));
}
try
{
Regex.Match("", expression);
}
catch (Exception e)
{
throw new ArgumentException($"Invalid regular expression!", nameof(expression), e);
}
}
}
}

View file

@ -0,0 +1,196 @@
/*
* 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.Text.RegularExpressions;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Settings.Browser.Filter;
namespace SafeExamBrowser.Browser.Filters.Rules
{
internal class SimplifiedRule : IRule
{
private const string URL_DELIMITER_PATTERN = @"(?:([^\:]*)\://)?(?:([^\:\@]*)(?:\:([^\@]*))?\@)?(?:([^/\:\?#]*))?(?:\:([0-9\*]*))?([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?";
private Regex fragment;
private Regex host;
private Regex path;
private int? port;
private Regex query;
private Regex scheme;
private Regex userInfo;
public FilterResult Result { get; private set; }
public void Initialize(FilterRuleSettings settings)
{
ValidateExpression(settings.Expression);
ParseExpression(settings.Expression);
Result = settings.Result;
}
public bool IsMatch(Request request)
{
var url = new Uri(request.Url, UriKind.Absolute);
var isMatch = true;
isMatch &= scheme == default(Regex) || scheme.IsMatch(url.Scheme);
isMatch &= userInfo == default(Regex) || userInfo.IsMatch(url.UserInfo);
isMatch &= host.IsMatch(url.Host);
isMatch &= !port.HasValue || port == url.Port;
isMatch &= path == default(Regex) || path.IsMatch(url.AbsolutePath);
isMatch &= query == default(Regex) || query.IsMatch(url.Query);
isMatch &= fragment == default(Regex) || fragment.IsMatch(url.Fragment);
return isMatch;
}
private void ParseExpression(string expression)
{
var match = Regex.Match(expression, URL_DELIMITER_PATTERN);
ParseScheme(match.Groups[1].Value);
ParseUserInfo(match.Groups[2].Value, match.Groups[3].Value);
ParseHost(match.Groups[4].Value);
ParsePort(match.Groups[5].Value);
ParsePath(match.Groups[6].Value);
ParseQuery(match.Groups[7].Value);
ParseFragment(match.Groups[8].Value);
}
private void ParseScheme(string expression)
{
if (!string.IsNullOrEmpty(expression))
{
expression = Regex.Escape(expression);
expression = ReplaceWildcard(expression);
scheme = Build(expression);
}
}
private void ParseUserInfo(string username, string password)
{
if (!string.IsNullOrEmpty(username))
{
var expression = default(string);
username = Regex.Escape(username);
password = Regex.Escape(password);
expression = string.IsNullOrEmpty(password) ? $@"{username}(:.*)?" : $@"{username}:{password}";
expression = ReplaceWildcard(expression);
userInfo = Build(expression);
}
}
private void ParseHost(string expression)
{
var isAlphanumeric = Regex.IsMatch(expression, @"^[a-zA-Z0-9]+$");
var matchExactSubdomain = expression.StartsWith(".");
expression = matchExactSubdomain ? expression.Substring(1) : expression;
expression = Regex.Escape(expression);
expression = ReplaceWildcard(expression);
if (!isAlphanumeric && !matchExactSubdomain)
{
expression = $@"(.+?\.)*{expression}";
}
host = Build(expression);
}
private void ParsePort(string expression)
{
if (int.TryParse(expression, out var port))
{
this.port = port;
}
}
private void ParsePath(string expression)
{
if (!string.IsNullOrWhiteSpace(expression) && !expression.Equals("/"))
{
expression = Regex.Escape(expression);
expression = ReplaceWildcard(expression);
expression = expression.EndsWith("/") ? $@"{expression}?" : $@"{expression}/?";
path = Build(expression);
}
}
private void ParseQuery(string expression)
{
if (!string.IsNullOrWhiteSpace(expression))
{
var noQueryAllowed = expression == ".";
if (noQueryAllowed)
{
expression = @"\??";
}
else
{
expression = Regex.Escape(expression);
expression = ReplaceWildcard(expression);
expression = $@"\??{expression}";
}
query = Build(expression);
}
}
private void ParseFragment(string expression)
{
if (!string.IsNullOrWhiteSpace(expression))
{
expression = Regex.Escape(expression);
expression = ReplaceWildcard(expression);
expression = $"#?{expression}";
fragment = Build(expression);
}
}
private Regex Build(string expression)
{
return new Regex($"^{expression}$", RegexOptions.IgnoreCase);
}
private string ReplaceWildcard(string expression)
{
return expression.Replace(@"\*", ".*");
}
private void ValidateExpression(string expression)
{
if (expression == default(string))
{
throw new ArgumentNullException(nameof(expression));
}
if (!Regex.IsMatch(expression, @"[a-zA-Z0-9\*]+"))
{
throw new ArgumentException("Expression must consist of at least one alphanumeric character or asterisk!", nameof(expression));
}
try
{
Regex.Match(expression, URL_DELIMITER_PATTERN);
}
catch (Exception e)
{
throw new ArgumentException("Expression is not a valid simplified filter expression!", nameof(expression), e);
}
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 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,9 +10,6 @@ using CefSharp;
namespace SafeExamBrowser.Browser.Handlers
{
/// <remarks>
/// See https://cefsharp.github.io/api/73.1.x/html/T_CefSharp_IContextMenuHandler.htm.
/// </remarks>
internal class ContextMenuHandler : IContextMenuHandler
{
public void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model)

View file

@ -0,0 +1,51 @@
/*
* 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.Collections.Generic;
using System.Threading.Tasks;
using CefSharp;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Browser.Wrapper;
namespace SafeExamBrowser.Browser.Handlers
{
internal class DialogHandler : IDialogHandler
{
internal event DialogRequestedEventHandler DialogRequested;
public bool OnFileDialog(IWebBrowser webBrowser, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, IFileDialogCallback callback)
{
var args = new DialogRequestedEventArgs
{
Element = mode.ToElement(),
InitialPath = defaultFilePath,
Operation = mode.ToOperation(),
Title = title
};
Task.Run(() =>
{
DialogRequested?.Invoke(args);
using (callback)
{
if (args.Success)
{
callback.Continue(new List<string> { args.FullPath });
}
else
{
callback.Cancel();
}
}
});
return true;
}
}
}

View file

@ -1,23 +1,21 @@
/*
* Copyright (c) 2019 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;
using SafeExamBrowser.Contracts.Browser;
namespace SafeExamBrowser.Browser.Handlers
{
/// <remarks>
/// See https://cefsharp.github.io/api/73.1.x/html/T_CefSharp_IDisplayHandler.htm.
/// </remarks>
internal class DisplayHandler : IDisplayHandler
{
public event FaviconChangedEventHandler FaviconChanged;
@ -37,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) 2019 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,80 +11,181 @@ using System.Collections.Concurrent;
using System.IO;
using System.Threading.Tasks;
using CefSharp;
using SafeExamBrowser.Contracts.Browser;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging;
using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.BrowserSettings;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using Syroot.Windows.IO;
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
namespace SafeExamBrowser.Browser.Handlers
{
/// <remarks>
/// See https://cefsharp.github.io/api/73.1.x/html/T_CefSharp_IDownloadHandler.htm.
/// </remarks>
internal class DownloadHandler : IDownloadHandler
{
private AppConfig appConfig;
private BrowserSettings settings;
private ConcurrentDictionary<int, DownloadFinishedCallback> callbacks;
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;
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
internal event DownloadAbortedEventHandler DownloadAborted;
internal event DownloadUpdatedEventHandler DownloadUpdated;
public DownloadHandler(AppConfig appConfig, BrowserSettings settings, ILogger logger)
internal DownloadHandler(AppConfig appConfig, ILogger logger, BrowserSettings settings, WindowSettings windowSettings)
{
this.appConfig = appConfig;
this.callbacks = new ConcurrentDictionary<int, DownloadFinishedCallback>();
this.downloads = new ConcurrentDictionary<int, Guid>();
this.logger = logger;
this.settings = settings;
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 extension = Path.GetExtension(uri.AbsolutePath);
var isConfigFile = String.Equals(extension, appConfig.ConfigurationFileExtension, StringComparison.OrdinalIgnoreCase);
var fileExtension = Path.GetExtension(downloadItem.SuggestedFileName);
var isConfigurationFile = false;
var url = downloadItem.Url;
var urlExtension = default(string);
logger.Debug($"Handling download request for '{uri}'.");
if (downloadItem.Url.StartsWith("data:"))
{
url = downloadItem.Url.Length <= 100 ? downloadItem.Url : downloadItem.Url.Substring(0, 100) + "...";
}
if (isConfigFile)
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, urlExtension, StringComparison.OrdinalIgnoreCase);
isConfigurationFile |= string.Equals(appConfig.ConfigurationFileMimeType, downloadItem.MimeType, StringComparison.OrdinalIgnoreCase);
logger.Debug($"Detected download request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")}.");
if (isConfigurationFile)
{
Task.Run(() => RequestConfigurationFileDownload(downloadItem, callback));
}
else if (settings.AllowDownloads)
{
logger.Debug($"Starting download of '{uri}'...");
using (callback)
{
callback.Continue(null, true);
}
Task.Run(() => HandleFileDownload(downloadItem, callback));
}
else
{
logger.Info($"Aborted download request 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());
}
}
public void OnDownloadUpdated(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IDownloadItemCallback callback)
{
var hasId = downloads.TryGetValue(downloadItem.Id, out var id);
if (hasId)
{
var state = new DownloadItemState(id)
{
Completion = downloadItem.PercentComplete / 100.0,
FullPath = downloadItem.FullPath,
IsCancelled = downloadItem.IsCancelled,
IsComplete = downloadItem.IsComplete,
Url = downloadItem.Url
};
Task.Run(() => DownloadUpdated?.Invoke(state));
}
if (downloadItem.IsComplete || downloadItem.IsCancelled)
{
if (callbacks.TryRemove(downloadItem.Id, out DownloadFinishedCallback finished) && finished != null)
logger.Debug($"Download of '{downloadItem.FullPath}' {(downloadItem.IsComplete ? "is complete" : "was cancelled")}.");
if (callbacks.TryRemove(downloadItem.Id, out var finished) && finished != null)
{
Task.Run(() => finished.Invoke(downloadItem.IsComplete, downloadItem.FullPath));
Task.Run(() => finished.Invoke(downloadItem.IsComplete, downloadItem.Url, downloadItem.FullPath));
}
logger.Debug($"Download of '{downloadItem.Url}' {(downloadItem.IsComplete ? "is complete" : "was cancelled")}.");
if (hasId)
{
downloads.TryRemove(downloadItem.Id, out _);
}
}
}
private void HandleFileDownload(DownloadItem downloadItem, IBeforeDownloadCallback callback)
{
var filePath = default(string);
var showDialog = settings.AllowCustomDownAndUploadLocation;
logger.Debug($"Handling download of file '{downloadItem.SuggestedFileName}'.");
if (!string.IsNullOrEmpty(settings.DownAndUploadDirectory))
{
filePath = Path.Combine(Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory), downloadItem.SuggestedFileName);
}
else
{
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.");
}
else
{
logger.Debug($"Automatically downloading file as '{filePath}'.");
}
downloads[downloadItem.Id] = Guid.NewGuid();
using (callback)
{
callback.Continue(filePath, showDialog);
}
}
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();
var args = new DownloadEventArgs { Url = downloadItem.Url };
logger.Debug($"Detected download request for configuration file '{downloadItem.Url}'.");
logger.Debug($"Handling download of configuration file '{downloadItem.SuggestedFileName}'.");
ConfigurationDownloadRequested?.Invoke(downloadItem.SuggestedFileName, args);
logger.Debug($"Download of configuration file '{downloadItem.Url}' was {(args.AllowDownload ? "granted" : "denied")}.");
if (args.AllowDownload)
{
@ -93,11 +194,17 @@ namespace SafeExamBrowser.Browser.Handlers
callbacks[downloadItem.Id] = args.Callback;
}
logger.Debug($"Starting download of configuration file '{downloadItem.SuggestedFileName}'...");
using (callback)
{
callback.Continue(args.DownloadPath, false);
}
}
else
{
logger.Debug($"Download of configuration file '{downloadItem.SuggestedFileName}' was cancelled.");
}
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 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,40 +8,68 @@
using System.Windows.Forms;
using CefSharp;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.UserInterface.Contracts;
namespace SafeExamBrowser.Browser.Handlers
{
/// <remarks>
/// See https://cefsharp.github.io/api/73.1.x/html/T_CefSharp_IKeyboardHandler.htm.
/// </remarks>
internal class KeyboardHandler : IKeyboardHandler
{
public event ActionRequestedEventHandler ReloadRequested;
public event ActionRequestedEventHandler ZoomInRequested;
public event ActionRequestedEventHandler ZoomOutRequested;
public event ActionRequestedEventHandler ZoomResetRequested;
internal event ActionRequestedEventHandler FindRequested;
internal event ActionRequestedEventHandler HomeNavigationRequested;
internal event ActionRequestedEventHandler ReloadRequested;
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)
{
var ctrl = modifiers.HasFlag(CefEventFlags.ControlDown);
var shift = modifiers.HasFlag(CefEventFlags.ShiftDown);
if (type == KeyType.KeyUp && ((keyCode == (int)Keys.Add && ctrl) || (keyCode == (int)Keys.D1 && ctrl && shift)))
if (type == KeyType.KeyUp)
{
ZoomInRequested?.Invoke();
}
if (type == KeyType.KeyUp && (keyCode == (int) Keys.Subtract || keyCode == (int) Keys.OemMinus) && ctrl)
{
ZoomOutRequested?.Invoke();
}
if (type == KeyType.KeyUp && (keyCode == (int) Keys.D0 || keyCode == (int) Keys.NumPad0) && ctrl)
{
ZoomResetRequested?.Invoke();
if (ctrl && keyCode == (int) Keys.F)
{
FindRequested?.Invoke();
}
if (keyCode == (int) Keys.Home)
{
HomeNavigationRequested?.Invoke();
}
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();
}
if (ctrl && (keyCode == (int) Keys.Subtract || keyCode == (int) Keys.OemMinus))
{
ZoomOutRequested?.Invoke();
}
if (ctrl && (keyCode == (int) Keys.D0 || keyCode == (int) Keys.NumPad0))
{
ZoomResetRequested?.Invoke();
}
if (keyCode == (int) Keys.Tab && keyCode == currentKeyDown)
{
TabPressed?.Invoke(shift);
}
}
currentKeyDown = null;
return false;
}
@ -54,6 +82,11 @@ namespace SafeExamBrowser.Browser.Handlers
return true;
}
if (type == KeyType.RawKeyDown || type == KeyType.KeyDown)
{
currentKeyDown = keyCode;
}
return false;
}
}

View file

@ -1,44 +0,0 @@
/*
* Copyright (c) 2019 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
{
/// <remarks>
/// See https://cefsharp.github.io/api/73.1.x/html/T_CefSharp_ILifeSpanHandler.htm.
/// </remarks>
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;
}
}
}

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