SEBWIN-788: Implemented scaffolding for wireless network credentials.

This commit is contained in:
Damian Büchel 2024-05-02 10:30:26 +02:00
parent e4a82e2f63
commit 4015e9a574
24 changed files with 395 additions and 35 deletions

View file

@ -33,6 +33,7 @@ using SafeExamBrowser.Server.Contracts;
using SafeExamBrowser.Server.Contracts.Data; using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.Settings; using SafeExamBrowser.Settings;
using SafeExamBrowser.Settings.Monitoring; using SafeExamBrowser.Settings.Monitoring;
using SafeExamBrowser.SystemComponents.Contracts.Network;
using SafeExamBrowser.SystemComponents.Contracts.Registry; using SafeExamBrowser.SystemComponents.Contracts.Registry;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog; using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
@ -61,6 +62,7 @@ namespace SafeExamBrowser.Client.UnitTests
private Mock<IIntegrityModule> integrityModule; private Mock<IIntegrityModule> integrityModule;
private Mock<ILogger> logger; private Mock<ILogger> logger;
private Mock<IMessageBox> messageBox; private Mock<IMessageBox> messageBox;
private Mock<INetworkAdapter> networkAdapter;
private Mock<IOperationSequence> operationSequence; private Mock<IOperationSequence> operationSequence;
private Mock<IRegistry> registry; private Mock<IRegistry> registry;
private Mock<IRuntimeProxy> runtimeProxy; private Mock<IRuntimeProxy> runtimeProxy;
@ -94,6 +96,7 @@ namespace SafeExamBrowser.Client.UnitTests
integrityModule = new Mock<IIntegrityModule>(); integrityModule = new Mock<IIntegrityModule>();
logger = new Mock<ILogger>(); logger = new Mock<ILogger>();
messageBox = new Mock<IMessageBox>(); messageBox = new Mock<IMessageBox>();
networkAdapter = new Mock<INetworkAdapter>();
operationSequence = new Mock<IOperationSequence>(); operationSequence = new Mock<IOperationSequence>();
registry = new Mock<IRegistry>(); registry = new Mock<IRegistry>();
runtimeProxy = new Mock<IRuntimeProxy>(); runtimeProxy = new Mock<IRuntimeProxy>();
@ -122,6 +125,7 @@ namespace SafeExamBrowser.Client.UnitTests
hashAlgorithm.Object, hashAlgorithm.Object,
logger.Object, logger.Object,
messageBox.Object, messageBox.Object,
networkAdapter.Object,
operationSequence.Object, operationSequence.Object,
registry.Object, registry.Object,
runtimeProxy.Object, runtimeProxy.Object,

View file

@ -35,6 +35,8 @@ using SafeExamBrowser.Proctoring.Contracts.Events;
using SafeExamBrowser.Server.Contracts; using SafeExamBrowser.Server.Contracts;
using SafeExamBrowser.Server.Contracts.Data; using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.Settings; using SafeExamBrowser.Settings;
using SafeExamBrowser.SystemComponents.Contracts.Network;
using SafeExamBrowser.SystemComponents.Contracts.Network.Events;
using SafeExamBrowser.SystemComponents.Contracts.Registry; using SafeExamBrowser.SystemComponents.Contracts.Registry;
using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog; using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
@ -57,6 +59,7 @@ namespace SafeExamBrowser.Client
private readonly IHashAlgorithm hashAlgorithm; private readonly IHashAlgorithm hashAlgorithm;
private readonly ILogger logger; private readonly ILogger logger;
private readonly IMessageBox messageBox; private readonly IMessageBox messageBox;
private readonly INetworkAdapter networkAdapter;
private readonly IOperationSequence operations; private readonly IOperationSequence operations;
private readonly IRegistry registry; private readonly IRegistry registry;
private readonly IRuntimeProxy runtime; private readonly IRuntimeProxy runtime;
@ -87,6 +90,7 @@ namespace SafeExamBrowser.Client
IHashAlgorithm hashAlgorithm, IHashAlgorithm hashAlgorithm,
ILogger logger, ILogger logger,
IMessageBox messageBox, IMessageBox messageBox,
INetworkAdapter networkAdapter,
IOperationSequence operations, IOperationSequence operations,
IRegistry registry, IRegistry registry,
IRuntimeProxy runtime, IRuntimeProxy runtime,
@ -106,6 +110,7 @@ namespace SafeExamBrowser.Client
this.hashAlgorithm = hashAlgorithm; this.hashAlgorithm = hashAlgorithm;
this.logger = logger; this.logger = logger;
this.messageBox = messageBox; this.messageBox = messageBox;
this.networkAdapter = networkAdapter;
this.operations = operations; this.operations = operations;
this.registry = registry; this.registry = registry;
this.runtime = runtime; this.runtime = runtime;
@ -214,6 +219,7 @@ namespace SafeExamBrowser.Client
ClientHost.ServerFailureActionRequested += ClientHost_ServerFailureActionRequested; ClientHost.ServerFailureActionRequested += ClientHost_ServerFailureActionRequested;
ClientHost.Shutdown += ClientHost_Shutdown; ClientHost.Shutdown += ClientHost_Shutdown;
displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged; displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged;
networkAdapter.CredentialsRequired += NetworkAdapter_CredentialsRequired;
registry.ValueChanged += Registry_ValueChanged; registry.ValueChanged += Registry_ValueChanged;
runtime.ConnectionLost += Runtime_ConnectionLost; runtime.ConnectionLost += Runtime_ConnectionLost;
systemMonitor.SessionChanged += SystemMonitor_SessionChanged; systemMonitor.SessionChanged += SystemMonitor_SessionChanged;
@ -690,6 +696,16 @@ namespace SafeExamBrowser.Client
} }
} }
private void NetworkAdapter_CredentialsRequired(CredentialsRequiredEventArgs args)
{
var dialog = uiFactory.CreateNetworkDialog("TODO", "TODO");
var result = dialog.Show();
args.Password = result.Password;
args.Success = result.Success;
args.Username = result.Username;
}
private void Operations_ActionRequired(ActionRequiredEventArgs args) private void Operations_ActionRequired(ActionRequiredEventArgs args)
{ {
switch (args) switch (args)

View file

@ -154,6 +154,7 @@ namespace SafeExamBrowser.Client
hashAlgorithm, hashAlgorithm,
logger, logger,
messageBox, messageBox,
networkAdapter,
sequence, sequence,
registry, registry,
runtimeProxy, runtimeProxy,

View file

@ -26,6 +26,26 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
InitializeClipboardSettings(settings); InitializeClipboardSettings(settings);
InitializeProctoringSettings(settings); InitializeProctoringSettings(settings);
RemoveLegacyBrowsers(settings); RemoveLegacyBrowsers(settings);
settings.Applications.Blacklist.Clear();
settings.Applications.Whitelist.Add(new WhitelistApplication { ExecutableName = "mspaint.exe", ShowInShell = true });
settings.Browser.AdditionalWindow.AllowAddressBar = true;
settings.Browser.MainWindow.AllowAddressBar = true;
settings.Browser.MainWindow.AllowDeveloperConsole = true;
settings.Browser.MainWindow.AllowBackwardNavigation = true;
settings.Browser.MainWindow.AllowForwardNavigation = true;
settings.Browser.MainWindow.ShowHomeButton = true;
settings.Browser.MainWindow.UrlPolicy = Settings.Browser.UrlPolicy.BeforeTitle;
settings.Keyboard.AllowPrintScreen = true;
settings.LogLevel = Settings.Logging.LogLevel.Debug;
settings.Security.AllowApplicationLogAccess = true;
settings.Security.ClipboardPolicy = ClipboardPolicy.Allow;
settings.Security.KioskMode = KioskMode.CreateNewDesktop;
settings.Security.QuitPasswordHash = default;
settings.Service.IgnoreService = true;
settings.Service.Policy = Settings.Service.ServicePolicy.Optional;
settings.Security.VersionRestrictions.Clear();
settings.Taskbar.ShowApplicationLog = true;
} }
private void AllowBrowserToolbarForReloading(AppSettings settings) private void AllowBrowserToolbarForReloading(AppSettings settings)

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.SystemComponents.Contracts.Network.Events
{
/// <summary>
///
/// </summary>
public class CredentialsRequiredEventArgs
{
/// <summary>
///
/// </summary>
public string Password { get; set; }
/// <summary>
///
/// </summary>
public bool Success { get; set; }
/// <summary>
///
/// </summary>
public string Username { get; set; }
}
}

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.SystemComponents.Contracts.Network.Events
{
/// <summary>
///
/// </summary>
public delegate void CredentialsRequiredEventHandler(CredentialsRequiredEventArgs args);
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using System.Collections.Generic; using System.Collections.Generic;
using SafeExamBrowser.SystemComponents.Contracts.Network.Events; using SafeExamBrowser.SystemComponents.Contracts.Network.Events;
@ -33,9 +32,14 @@ namespace SafeExamBrowser.SystemComponents.Contracts.Network
event ChangedEventHandler Changed; event ChangedEventHandler Changed;
/// <summary> /// <summary>
/// Attempts to connect to the wireless network with the given ID. /// Fired when credentials are required to connect to a network.
/// </summary> /// </summary>
void ConnectToWirelessNetwork(Guid id); event CredentialsRequiredEventHandler CredentialsRequired;
/// <summary>
/// Attempts to connect to the wireless network with the given name.
/// </summary>
void ConnectToWirelessNetwork(string name);
/// <summary> /// <summary>
/// Retrieves all currently available wireless networks. /// Retrieves all currently available wireless networks.

View file

@ -6,8 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
namespace SafeExamBrowser.SystemComponents.Contracts.Network namespace SafeExamBrowser.SystemComponents.Contracts.Network
{ {
/// <summary> /// <summary>
@ -15,11 +13,6 @@ namespace SafeExamBrowser.SystemComponents.Contracts.Network
/// </summary> /// </summary>
public interface IWirelessNetwork public interface IWirelessNetwork
{ {
/// <summary>
/// The unique identifier of the network.
/// </summary>
Guid Id { get; }
/// <summary> /// <summary>
/// The network name. /// The network name.
/// </summary> /// </summary>

View file

@ -58,6 +58,8 @@
<Compile Include="Audio\Events\VolumeChangedEventHandler.cs" /> <Compile Include="Audio\Events\VolumeChangedEventHandler.cs" />
<Compile Include="Audio\IAudio.cs" /> <Compile Include="Audio\IAudio.cs" />
<Compile Include="IFileSystem.cs" /> <Compile Include="IFileSystem.cs" />
<Compile Include="Network\Events\CredentialsRequiredEventArgs.cs" />
<Compile Include="Network\Events\CredentialsRequiredEventHandler.cs" />
<Compile Include="Registry\Events\RegistryValueChangedEventHandler.cs" /> <Compile Include="Registry\Events\RegistryValueChangedEventHandler.cs" />
<Compile Include="Registry\IRegistry.cs" /> <Compile Include="Registry\IRegistry.cs" />
<Compile Include="IRemoteSessionDetector.cs" /> <Compile Include="IRemoteSessionDetector.cs" />

View file

@ -10,7 +10,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Timers;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.Network; using SafeExamBrowser.SystemComponents.Contracts.Network;
using SafeExamBrowser.SystemComponents.Contracts.Network.Events; using SafeExamBrowser.SystemComponents.Contracts.Network.Events;
@ -18,12 +17,22 @@ using SafeExamBrowser.WindowsApi.Contracts;
using SimpleWifi; using SimpleWifi;
using SimpleWifi.Win32; using SimpleWifi.Win32;
using SimpleWifi.Win32.Interop; using SimpleWifi.Win32.Interop;
using Timer = System.Timers.Timer;
namespace SafeExamBrowser.SystemComponents.Network namespace SafeExamBrowser.SystemComponents.Network
{ {
/// <summary>
/// Switch to the following WiFi library:
/// https://github.com/emoacht/ManagedNativeWifi
/// https://www.nuget.org/packages/ManagedNativeWifi
///
/// Potentially useful:
/// https://learn.microsoft.com/en-us/dotnet/api/system.net.networkinformation.networkinterface?view=netframework-4.8
/// </summary>
public class NetworkAdapter : INetworkAdapter public class NetworkAdapter : INetworkAdapter
{ {
private readonly object @lock = new object(); private readonly object @lock = new object();
private readonly ILogger logger; private readonly ILogger logger;
private readonly INativeMethods nativeMethods; private readonly INativeMethods nativeMethods;
private readonly List<WirelessNetwork> wirelessNetworks; private readonly List<WirelessNetwork> wirelessNetworks;
@ -35,6 +44,7 @@ namespace SafeExamBrowser.SystemComponents.Network
public ConnectionType Type { get; private set; } public ConnectionType Type { get; private set; }
public event ChangedEventHandler Changed; public event ChangedEventHandler Changed;
public event CredentialsRequiredEventHandler CredentialsRequired;
public NetworkAdapter(ILogger logger, INativeMethods nativeMethods) public NetworkAdapter(ILogger logger, INativeMethods nativeMethods)
{ {
@ -43,22 +53,27 @@ namespace SafeExamBrowser.SystemComponents.Network
this.wirelessNetworks = new List<WirelessNetwork>(); this.wirelessNetworks = new List<WirelessNetwork>();
} }
public void ConnectToWirelessNetwork(Guid id) public void ConnectToWirelessNetwork(string name)
{ {
lock (@lock) lock (@lock)
{ {
var network = wirelessNetworks.FirstOrDefault(n => n.Id == id); var network = wirelessNetworks.FirstOrDefault(n => n.Name == name);
if (network != default) if (network != default)
{ {
try try
{ {
var request = new AuthRequest(network.AccessPoint); var accessPoint = network.AccessPoint;
var request = new AuthRequest(accessPoint);
logger.Info($"Attempting to connect to '{network.Name}'..."); if (accessPoint.HasProfile || accessPoint.IsConnected || TryGetCredentials(request))
{
logger.Info($"Attempting to connect to wireless network '{network.Name}' with{(request.Password == default ? "out" : "")} credentials...");
network.AccessPoint.ConnectAsync(request, false, (success) => AccessPoint_OnConnectCompleted(network.Name, success)); // TODO: Retry resp. alert of password error on failure and then ignore profile?!
Status = ConnectionStatus.Connecting; accessPoint.ConnectAsync(request, false, (success) => ConnectionAttemptCompleted(network.Name, success));
Status = ConnectionStatus.Connecting;
}
} }
catch (Exception e) catch (Exception e)
{ {
@ -67,7 +82,7 @@ namespace SafeExamBrowser.SystemComponents.Network
} }
else else
{ {
logger.Warn($"Could not find network with id '{id}'!"); logger.Warn($"Could not find wireless network '{name}'!");
} }
} }
@ -111,7 +126,7 @@ namespace SafeExamBrowser.SystemComponents.Network
} }
} }
private void AccessPoint_OnConnectCompleted(string name, bool success) private void ConnectionAttemptCompleted(string name, bool success)
{ {
lock (@lock) lock (@lock)
{ {
@ -129,12 +144,28 @@ namespace SafeExamBrowser.SystemComponents.Network
} }
} }
private bool TryGetCredentials(AuthRequest request)
{
var args = new CredentialsRequiredEventArgs();
CredentialsRequired?.Invoke(args);
if (args.Success)
{
request.Password = args.Password;
request.Username = args.Username;
}
return args.Success;
}
private void Update() private void Update()
{ {
try try
{ {
lock (@lock) lock (@lock)
{ {
var current = default(WirelessNetwork);
var hasInternet = nativeMethods.HasInternetConnection(); var hasInternet = nativeMethods.HasInternetConnection();
var hasWireless = !wifi.NoWifiAvailable && !IsTurnedOff(); var hasWireless = !wifi.NoWifiAvailable && !IsTurnedOff();
var isConnecting = Status == ConnectionStatus.Connecting; var isConnecting = Status == ConnectionStatus.Connecting;
@ -144,12 +175,13 @@ namespace SafeExamBrowser.SystemComponents.Network
if (hasWireless) if (hasWireless)
{ {
foreach (var accessPoint in wifi.GetAccessPoints()) foreach (var wirelessNetwork in wifi.GetAccessPoints().Select(a => ToWirelessNetwork(a)))
{ {
// The user may only connect to an already configured or connected wireless network! wirelessNetworks.Add(wirelessNetwork);
if (accessPoint.HasProfile || accessPoint.IsConnected)
if (wirelessNetwork.Status == ConnectionStatus.Connected)
{ {
wirelessNetworks.Add(ToWirelessNetwork(accessPoint)); current = wirelessNetwork;
} }
} }
} }
@ -159,7 +191,7 @@ namespace SafeExamBrowser.SystemComponents.Network
if (previousStatus != ConnectionStatus.Connected && Status == ConnectionStatus.Connected) if (previousStatus != ConnectionStatus.Connected && Status == ConnectionStatus.Connected)
{ {
logger.Info("Connection established."); logger.Info($"Connection established ({Type}{(current != default ? $", {current.Name}" : "")}).");
} }
if (previousStatus != ConnectionStatus.Disconnected && Status == ConnectionStatus.Disconnected) if (previousStatus != ConnectionStatus.Disconnected && Status == ConnectionStatus.Disconnected)

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using SafeExamBrowser.SystemComponents.Contracts.Network; using SafeExamBrowser.SystemComponents.Contracts.Network;
using SimpleWifi; using SimpleWifi;
@ -16,14 +15,8 @@ namespace SafeExamBrowser.SystemComponents.Network
{ {
internal AccessPoint AccessPoint { get; set; } internal AccessPoint AccessPoint { get; set; }
public Guid Id { get; }
public string Name { get; set; } public string Name { get; set; }
public int SignalStrength { get; set; } public int SignalStrength { get; set; }
public ConnectionStatus Status { get; set; } public ConnectionStatus Status { get; set; }
public WirelessNetwork()
{
Id = Guid.NewGuid();
}
} }
} }

View file

@ -83,6 +83,11 @@ namespace SafeExamBrowser.UserInterface.Contracts
/// </summary> /// </summary>
ISystemControl CreateNetworkControl(INetworkAdapter adapter, Location location); ISystemControl CreateNetworkControl(INetworkAdapter adapter, Location location);
/// <summary>
/// Creates a network dialog with the given message and title.
/// </summary>
INetworkDialog CreateNetworkDialog(string message, string title);
/// <summary> /// <summary>
/// Creates a notification control for the given notification, initialized for the specified location. /// Creates a notification control for the given notification, initialized for the specified location.
/// </summary> /// </summary>

View file

@ -96,11 +96,13 @@
<Compile Include="Windows\Data\ExamSelectionDialogResult.cs" /> <Compile Include="Windows\Data\ExamSelectionDialogResult.cs" />
<Compile Include="Windows\Data\LockScreenOption.cs" /> <Compile Include="Windows\Data\LockScreenOption.cs" />
<Compile Include="Windows\Data\LockScreenResult.cs" /> <Compile Include="Windows\Data\LockScreenResult.cs" />
<Compile Include="Windows\Data\NetworkDialogResult.cs" />
<Compile Include="Windows\Data\ServerFailureDialogResult.cs" /> <Compile Include="Windows\Data\ServerFailureDialogResult.cs" />
<Compile Include="Windows\Events\WindowClosedEventHandler.cs" /> <Compile Include="Windows\Events\WindowClosedEventHandler.cs" />
<Compile Include="Windows\Events\WindowClosingEventHandler.cs" /> <Compile Include="Windows\Events\WindowClosingEventHandler.cs" />
<Compile Include="Windows\IExamSelectionDialog.cs" /> <Compile Include="Windows\IExamSelectionDialog.cs" />
<Compile Include="Windows\ILockScreen.cs" /> <Compile Include="Windows\ILockScreen.cs" />
<Compile Include="Windows\INetworkDialog.cs" />
<Compile Include="Windows\IPasswordDialog.cs" /> <Compile Include="Windows\IPasswordDialog.cs" />
<Compile Include="Windows\Data\PasswordDialogResult.cs" /> <Compile Include="Windows\Data\PasswordDialogResult.cs" />
<Compile Include="Proctoring\IProctoringFinalizationDialog.cs" /> <Compile Include="Proctoring\IProctoringFinalizationDialog.cs" />

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.UserInterface.Contracts.Windows.Data
{
/// <summary>
/// Defines the user interaction result of an <see cref="INetworkDialog"/>.
/// </summary>
public class NetworkDialogResult
{
/// <summary>
/// The password entered by the user, or <c>default(string)</c> if the interaction was unsuccessful.
/// </summary>
public string Password { get; set; }
/// <summary>
/// Indicates whether the user confirmed the dialog or not.
/// </summary>
public bool Success { get; set; }
/// <summary>
/// The username entered by the user, or <c>default(string)</c> if no username is required or the interaction was unsuccessful.
/// </summary>
public string Username { get; set; }
}
}

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.UserInterface.Contracts.Windows.Data;
namespace SafeExamBrowser.UserInterface.Contracts.Windows
{
/// <summary>
/// Defines the functionality of a network dialog.
/// </summary>
public interface INetworkDialog : IWindow
{
/// <summary>
/// Shows the dialog as topmost window. If a parent window is specified, the dialog is rendered modally for the given parent.
/// </summary>
NetworkDialogResult Show(IWindow parent = null);
}
}

View file

@ -100,7 +100,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.ActionCenter
{ {
var button = new NetworkButton(network); var button = new NetworkButton(network);
button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Id); button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Name);
if (network.Status == ConnectionStatus.Connected) if (network.Status == ConnectionStatus.Connected)
{ {

View file

@ -112,7 +112,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls.Taskbar
{ {
var button = new NetworkButton(network); var button = new NetworkButton(network);
button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Id); button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Name);
if (network.Status == ConnectionStatus.Connected) if (network.Status == ConnectionStatus.Connected)
{ {

View file

@ -168,6 +168,9 @@
<DependentUpon>LogWindow.xaml</DependentUpon> <DependentUpon>LogWindow.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="MessageBoxFactory.cs" /> <Compile Include="MessageBoxFactory.cs" />
<Compile Include="Windows\NetworkDialog.xaml.cs">
<DependentUpon>NetworkDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Windows\PasswordDialog.xaml.cs"> <Compile Include="Windows\PasswordDialog.xaml.cs">
<DependentUpon>PasswordDialog.xaml</DependentUpon> <DependentUpon>PasswordDialog.xaml</DependentUpon>
</Compile> </Compile>
@ -394,6 +397,10 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="Windows\NetworkDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Windows\ProctoringFinalizationDialog.xaml"> <Page Include="Windows\ProctoringFinalizationDialog.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

View file

@ -143,6 +143,11 @@ namespace SafeExamBrowser.UserInterface.Desktop
} }
} }
public INetworkDialog CreateNetworkDialog(string message, string title)
{
return Application.Current.Dispatcher.Invoke(() => new NetworkDialog(message, title, text));
}
public INotificationControl CreateNotificationControl(INotification notification, Location location) public INotificationControl CreateNotificationControl(INotification notification, Location location)
{ {
if (location == Location.ActionCenter) if (location == Location.ActionCenter)

View file

@ -0,0 +1,53 @@
<Window x:Class="SafeExamBrowser.UserInterface.Desktop.Windows.NetworkDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:fa="http://schemas.fontawesome.io/icons/"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop.Windows"
mc:Ignorable="d" Height="350" Width="450" ResizeMode="NoResize" Topmost="True">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Templates/Colors.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid FocusManager.FocusedElement="{Binding ElementName=Password}">
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<fa:ImageAwesome Grid.Column="0" Foreground="LightGray" Icon="Wifi" Margin="25" Width="50" />
<Grid Grid.Column="1" Margin="0,0,25,25">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Name="Message" Margin="0,0,0,5" TextWrapping="WrapWithOverflow" VerticalAlignment="Bottom" />
<StackPanel Grid.Row="1" Orientation="Vertical">
<Label Name="UsernameLabel" Target="{Binding ElementName=Username}" />
<TextBox Name="Username" Height="25" Margin="0,0,0,5" VerticalContentAlignment="Center" />
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Vertical">
<Label Name="PasswordLabel" Target="{Binding ElementName=Password}" />
<PasswordBox Grid.Row="2" Name="Password" Height="25" VerticalContentAlignment="Center" />
</StackPanel>
</Grid>
</Grid>
</Grid>
<Grid Grid.Row="1" Background="{StaticResource BackgroundBrush}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<WrapPanel Orientation="Horizontal" Margin="25,0" HorizontalAlignment="Right" VerticalAlignment="Center">
<Button Name="ConfirmButton" Cursor="Hand" Margin="10,0" Padding="10,5" MinWidth="75" />
<Button Name="CancelButton" Cursor="Hand" Padding="10,5" MinWidth="75" />
</WrapPanel>
</Grid>
</Grid>
</Window>

View file

@ -0,0 +1,117 @@
/*
* 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;
using System.Windows.Input;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
namespace SafeExamBrowser.UserInterface.Desktop.Windows
{
public partial class NetworkDialog : Window, INetworkDialog
{
private readonly IText text;
private WindowClosedEventHandler closed;
private WindowClosingEventHandler closing;
event WindowClosedEventHandler IWindow.Closed
{
add { closed += value; }
remove { closed -= value; }
}
event WindowClosingEventHandler IWindow.Closing
{
add { closing += value; }
remove { closing -= value; }
}
internal NetworkDialog(string message, string title, IText text)
{
this.text = text;
InitializeComponent();
InitializeNetworkDialog(message, title);
}
public void BringToForeground()
{
Dispatcher.Invoke(Activate);
}
public NetworkDialogResult Show(IWindow parent = null)
{
return Dispatcher.Invoke(() =>
{
var result = new NetworkDialogResult { Success = false };
if (parent is Window)
{
Owner = parent as Window;
WindowStartupLocation = WindowStartupLocation.CenterOwner;
}
if (ShowDialog() is true)
{
result.Password = Password.Password;
result.Success = true;
result.Username = Username.Text;
}
return result;
});
}
private void InitializeNetworkDialog(string message, string title)
{
Message.Text = message;
// TODO
PasswordLabel.Content = "Password";
Title = title;
// TODO
UsernameLabel.Content = "Username";
WindowStartupLocation = WindowStartupLocation.CenterScreen;
Closed += (o, args) => closed?.Invoke();
Closing += (o, args) => closing?.Invoke();
Loaded += (o, args) => Activate();
CancelButton.Content = text.Get(TextKey.PasswordDialog_Cancel);
CancelButton.Click += CancelButton_Click;
ConfirmButton.Content = text.Get(TextKey.PasswordDialog_Confirm);
ConfirmButton.Click += ConfirmButton_Click;
Password.KeyDown += Password_KeyDown;
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
private void Password_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
DialogResult = true;
Close();
}
}
}
}

View file

@ -100,7 +100,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.ActionCenter
{ {
var button = new NetworkButton(network); var button = new NetworkButton(network);
button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Id); button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Name);
if (network.Status == ConnectionStatus.Connected) if (network.Status == ConnectionStatus.Connected)
{ {

View file

@ -112,7 +112,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls.Taskbar
{ {
var button = new NetworkButton(network); var button = new NetworkButton(network);
button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Id); button.NetworkSelected += (o, args) => adapter.ConnectToWirelessNetwork(network.Name);
if (network.Status == ConnectionStatus.Connected) if (network.Status == ConnectionStatus.Connected)
{ {

View file

@ -143,6 +143,12 @@ namespace SafeExamBrowser.UserInterface.Mobile
} }
} }
public INetworkDialog CreateNetworkDialog(string message, string title)
{
// TODO
throw new System.NotImplementedException();
}
public INotificationControl CreateNotificationControl(INotification notification, Location location) public INotificationControl CreateNotificationControl(INotification notification, Location location)
{ {
if (location == Location.ActionCenter) if (location == Location.ActionCenter)