Public Access
Desktop: call device unlock what this platform calls it, not Windows Hello everywhere
This commit is contained in:
1 parent
079914bfb4
commit
a6d3924e0d
6 files changed
+161
-10
No files matched your search
@@ -136,7 +136,7 @@
|
||||
<Border Classes="settingscard">
|
||||
<StackPanel Margin="24,20" Spacing="10">
|
||||
<TextBlock Classes="settingsrowcaption"
|
||||
Text="Unlocking with Windows Hello moved to Security — it registers this machine rather than a preference of this screen's, and Security is where the rest of this machine's trust facts live." />
|
||||
Text="Unlocking with this machine's device key moved to Security — it registers this machine rather than a preference of this screen's, and Security is where the rest of this machine's trust facts live." />
|
||||
<TextBlock Classes="settingsrowcaption"
|
||||
Text="Terminal font, cursor and scrollback — the renderer hard-codes those three. Text size above is the one that is not." />
|
||||
<TextBlock Classes="settingsrowcaption"
|
||||
|
||||
@@ -70,15 +70,15 @@
|
||||
<Border Classes="settingsrow last">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0" Spacing="6" Margin="0,0,16,0" MaxWidth="640">
|
||||
<TextBlock Classes="settingsrowtitle" Text="Windows Hello on this machine" />
|
||||
<TextBlock Classes="settingsrowtitle" Text="{Binding DeviceUnlock.Name}" />
|
||||
<TextBlock Classes="settingsrowcaption"
|
||||
Text="Registers this machine so a later launch can open the keychain with a Windows confirmation instead of your passphrase. Your passphrase keeps working."
|
||||
Text="{Binding DeviceUnlock.Offer}"
|
||||
IsVisible="{Binding CanRegisterDevice}" />
|
||||
<TextBlock Classes="settingsrowcaption"
|
||||
Text="Registered · the TPM holds a device key that can open the keychain with a Windows confirmation."
|
||||
Text="{Binding DeviceUnlock.Registered}"
|
||||
IsVisible="{Binding CanForgetDevice}" />
|
||||
<TextBlock Classes="settingsrowcaption"
|
||||
Text="This machine has nowhere to keep a device key, so the keychain will keep asking for your passphrase. That needs a TPM and a Windows keystore willing to release the key."
|
||||
Text="{Binding DeviceUnlock.Unavailable}"
|
||||
IsVisible="{Binding HasNoDeviceKeyOption}" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1" Classes="accent" Height="38" Content="REGISTER"
|
||||
|
||||
@@ -44,15 +44,15 @@
|
||||
IsEnabled="{Binding !IsBusy}" />
|
||||
<!--
|
||||
Shown only when this machine has both a registered wrap and a keystore still willing to release the
|
||||
key. Absent rather than disabled, because a greyed-out "Use Windows Hello" on a machine that never
|
||||
key. Absent rather than disabled, because a greyed-out "Use Touch ID" on a machine that never
|
||||
had it invites the reading that something is broken — and the passphrase box beside it is not a
|
||||
fallback, it is the ordinary way in.
|
||||
-->
|
||||
<Button Classes="ghost" Content="USE WINDOWS HELLO"
|
||||
<Button Classes="ghost" Content="{Binding DeviceUnlock.Button}"
|
||||
Command="{Binding UnlockWithDeviceCommand}"
|
||||
IsEnabled="{Binding !IsBusy}"
|
||||
IsVisible="{Binding CanUnlockWithDevice}"
|
||||
ToolTip.Tip="Opens the keychain with this machine's device key. Windows will ask you to confirm." />
|
||||
ToolTip.Tip="{Binding DeviceUnlock.Tip}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Classes="hint" Text="{Binding StatusMessage}" TextWrapping="Wrap" />
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
namespace DodoSSH.Client.Shell.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// What unlocking with this machine's device key is called here, and what it asks the person for.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The words used to be Windows's everywhere: "USE WINDOWS HELLO" on a Mac, "Waiting for Windows…" over a
|
||||
/// Linux PIN prompt, and a settings row that told a MacBook it needed a TPM. Each desktop has its own device
|
||||
/// key store with its own gesture — see <c>WindowsDeviceKeyStore</c>, <c>MacDeviceKeyStore</c> and
|
||||
/// <c>LinuxDeviceKeyStore</c> — and a button has to name the one the person is about to be shown, or it reads
|
||||
/// as a feature for some other machine.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// One record rather than a property per sentence on the shell, so that the five places these words appear
|
||||
/// cannot disagree about which platform they are on.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="Name">What the feature is called, as a settings row's title.</param>
|
||||
/// <param name="Button">The unlock card's button, in the capitals every button here uses.</param>
|
||||
/// <param name="Wait">The status line while the platform's prompt is up.</param>
|
||||
/// <param name="Gesture">What opens the keychain instead of the passphrase, to finish a sentence.</param>
|
||||
/// <param name="Keeper">What holds the key, to finish "Registered · …".</param>
|
||||
/// <param name="Needs">What a machine with no device key store lacks, as a sentence of its own.</param>
|
||||
internal sealed record DeviceUnlockWording(
|
||||
string Name,
|
||||
string Button,
|
||||
string Wait,
|
||||
string Gesture,
|
||||
string Keeper,
|
||||
string Needs)
|
||||
{
|
||||
internal static DeviceUnlockWording Windows { get; } = new(
|
||||
"Windows Hello on this machine",
|
||||
"USE WINDOWS HELLO",
|
||||
"Waiting for Windows…",
|
||||
"a Windows confirmation",
|
||||
"the TPM holds a device key",
|
||||
"That needs a TPM and a Windows keystore willing to release the key.");
|
||||
|
||||
internal static DeviceUnlockWording Mac { get; } = new(
|
||||
"Touch ID on this Mac",
|
||||
"USE TOUCH ID",
|
||||
"Waiting for Touch ID…",
|
||||
"Touch ID or your login password",
|
||||
"the Secure Enclave holds a device key",
|
||||
"That needs a Mac with a Secure Enclave and a login password set.");
|
||||
|
||||
internal static DeviceUnlockWording Linux { get; } = new(
|
||||
"A device PIN on this machine",
|
||||
"USE DEVICE PIN",
|
||||
"Waiting for your PIN…",
|
||||
"a PIN the TPM checks",
|
||||
"the TPM holds a device key",
|
||||
"That needs a TPM 2.0 this account is allowed to use.");
|
||||
|
||||
internal static DeviceUnlockWording Android { get; } = new(
|
||||
"Fingerprint unlock on this phone",
|
||||
"USE FINGERPRINT",
|
||||
"Waiting for your fingerprint…",
|
||||
"your fingerprint",
|
||||
"the phone's keystore holds a device key",
|
||||
"That needs a screen lock and a keystore willing to release the key.");
|
||||
|
||||
/// <summary>The wording for the platform this process is running on.</summary>
|
||||
internal static DeviceUnlockWording ForThisPlatform { get; } =
|
||||
OperatingSystem.IsAndroid() ? Android
|
||||
: OperatingSystem.IsMacOS() ? Mac
|
||||
: OperatingSystem.IsLinux() ? Linux
|
||||
: Windows;
|
||||
|
||||
/// <summary>The settings caption for a machine that can register.</summary>
|
||||
internal string Offer =>
|
||||
$"Registers this machine so a later launch can open the keychain with {Gesture} instead of your passphrase. Your passphrase keeps working.";
|
||||
|
||||
/// <summary>The settings caption for a machine that has.</summary>
|
||||
internal string Registered => $"Registered · {Keeper} that can open the keychain with {Gesture}.";
|
||||
|
||||
/// <summary>The settings caption for a machine that cannot.</summary>
|
||||
internal string Unavailable =>
|
||||
$"This machine has nowhere to keep a device key, so the keychain will keep asking for your passphrase. {Needs}";
|
||||
|
||||
/// <summary>The unlock button's tooltip.</summary>
|
||||
internal string Tip => $"Opens the keychain with this machine's device key, after {Gesture}.";
|
||||
}
|
||||
@@ -2819,8 +2819,10 @@ internal sealed partial class MainWindowViewModel : ObservableObject, IAsyncDisp
|
||||
/// Naming the wrong operating system here is not cosmetic — it is the sentence a user reads while
|
||||
/// deciding whether the prompt in front of them is the one this application asked for.
|
||||
/// </remarks>
|
||||
private static string GestureWait =>
|
||||
OperatingSystem.IsAndroid() ? "Waiting for your fingerprint…" : "Waiting for Windows…";
|
||||
private static string GestureWait => DeviceUnlockWording.ForThisPlatform.Wait;
|
||||
|
||||
/// <summary>What unlocking with the device key is called on this platform; the views read it whole.</summary>
|
||||
internal DeviceUnlockWording DeviceUnlock { get; } = DeviceUnlockWording.ForThisPlatform;
|
||||
|
||||
/// <summary>Opens the vault with this machine's device key instead of the passphrase.</summary>
|
||||
/// <remarks>
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
using DodoSSH.Client.Shell.ViewModels;
|
||||
|
||||
namespace DodoSSH.Client.App.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// That unlocking with the device key is described in the words of the platform it runs on.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The unlock card offered "USE WINDOWS HELLO" on a Mac and the settings page told a Linux machine it needed a
|
||||
/// Windows keystore. Each platform's wording is checked for every other platform's names, because that is the
|
||||
/// mistake: a sentence borrowed from the one platform the feature was written on first.
|
||||
/// </remarks>
|
||||
public sealed class DeviceUnlockWordingTests
|
||||
{
|
||||
public static TheoryData<string> Platforms => ["Windows", "Mac", "Linux", "Android"];
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Platforms))]
|
||||
public void EachPlatform_NamesOnlyItsOwnGesture(string platform)
|
||||
{
|
||||
var wording = For(platform);
|
||||
|
||||
var foreign = new Dictionary<string, string[]>(StringComparer.Ordinal)
|
||||
{
|
||||
["Windows"] = ["Touch ID", "Secure Enclave", "fingerprint", "PIN"],
|
||||
["Mac"] = ["Windows", "TPM", "fingerprint", "PIN"],
|
||||
["Linux"] = ["Windows", "Touch ID", "Secure Enclave", "fingerprint"],
|
||||
["Android"] = ["Windows", "TPM", "Touch ID", "Secure Enclave"],
|
||||
}[platform];
|
||||
|
||||
string[] said =
|
||||
[wording.Name, wording.Button, wording.Wait, wording.Offer, wording.Registered, wording.Unavailable, wording.Tip];
|
||||
|
||||
foreach (var sentence in said)
|
||||
{
|
||||
sentence.ShouldNotBeNullOrWhiteSpace();
|
||||
|
||||
foreach (var name in foreign)
|
||||
{
|
||||
sentence.ShouldNotContain(name, Case.Sensitive, $"{platform}: \"{sentence}\"");
|
||||
}
|
||||
}
|
||||
|
||||
wording.Button.ShouldBe(wording.Button.ToUpperInvariant(), "every button on these screens is in capitals");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TheWordingInUse_IsThisMachines()
|
||||
{
|
||||
var expected = OperatingSystem.IsMacOS() ? DeviceUnlockWording.Mac
|
||||
: OperatingSystem.IsLinux() ? DeviceUnlockWording.Linux
|
||||
: DeviceUnlockWording.Windows;
|
||||
|
||||
DeviceUnlockWording.ForThisPlatform.ShouldBeSameAs(expected);
|
||||
}
|
||||
|
||||
private static DeviceUnlockWording For(string platform) => platform switch
|
||||
{
|
||||
"Windows" => DeviceUnlockWording.Windows,
|
||||
"Mac" => DeviceUnlockWording.Mac,
|
||||
"Linux" => DeviceUnlockWording.Linux,
|
||||
_ => DeviceUnlockWording.Android,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user