Skip to content

Commit 2cc296d

Browse files
authored
Simplify enum mapping & update specification links (#368)
1 parent 7b92f4a commit 2cc296d

10 files changed

+77
-90
lines changed

Src/Fido2.Models/Converters/EnumNameMapper.cs

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ namespace Fido2NetLib;
99
public static class EnumNameMapper<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum>
1010
where TEnum: struct, Enum
1111
{
12-
private static readonly Dictionary<TEnum, string> valueToNames = GetIdToNameMap();
13-
private static readonly Dictionary<string, TEnum> namesToValues = Invert(valueToNames);
12+
private static readonly Dictionary<TEnum, string> s_valueToNames = GetIdToNameMap();
13+
private static readonly Dictionary<string, TEnum> s_namesToValues = Invert(s_valueToNames);
1414

1515
private static Dictionary<string, TEnum> Invert(Dictionary<TEnum, string> map)
1616
{
@@ -24,42 +24,19 @@ private static Dictionary<string, TEnum> Invert(Dictionary<TEnum, string> map)
2424
return result;
2525
}
2626

27-
public static bool TryGetValue(string name, bool ignoreCase, out TEnum value)
28-
{
29-
if (namesToValues.TryGetValue(name, out value))
30-
{
31-
if (!ignoreCase && !valueToNames[value].Equals(name, StringComparison.Ordinal))
32-
{
33-
value = default;
34-
35-
return false;
36-
}
37-
else
38-
{
39-
return true;
40-
}
41-
}
42-
else
43-
{
44-
value = default;
45-
46-
return false;
47-
}
48-
}
49-
5027
public static bool TryGetValue(string name, out TEnum value)
5128
{
52-
return namesToValues.TryGetValue(name, out value);
29+
return s_namesToValues.TryGetValue(name, out value);
5330
}
5431

5532
public static string GetName(TEnum value)
5633
{
57-
return valueToNames[value];
34+
return s_valueToNames[value];
5835
}
5936

6037
public static IEnumerable<string> GetNames()
6138
{
62-
return namesToValues.Keys;
39+
return s_namesToValues.Keys;
6340
}
6441

6542
private static Dictionary<TEnum, string> GetIdToNameMap()

Src/Fido2.Models/CredentialCreateOptions.cs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ public static CredentialCreateOptions FromJson(string json)
112112
}
113113
}
114114

115+
#nullable enable
116+
115117
public sealed class PubKeyCredParam
116118
{
117119
/// <summary>
@@ -148,7 +150,6 @@ public PubKeyCredParam(COSE.Algorithm alg, PublicKeyCredentialType type = Public
148150
public static readonly PubKeyCredParam Ed25519 = new(COSE.Algorithm.EdDSA);
149151
}
150152

151-
#nullable enable
152153
/// <summary>
153154
/// PublicKeyCredentialRpEntity
154155
/// </summary>
@@ -181,20 +182,25 @@ public PublicKeyCredentialRpEntity(string id, string name, string? icon = null)
181182

182183
/// <summary>
183184
/// WebAuthn Relying Parties may use the AuthenticatorSelectionCriteria dictionary to specify their requirements regarding authenticator attributes.
184-
/// https://w3c.github.io/webauthn/#dictionary-authenticatorSelection
185+
/// https://www.w3.org/TR/webauthn-2/#dictionary-authenticatorSelection
185186
/// </summary>
186187
public class AuthenticatorSelection
187188
{
188189
/// <summary>
189-
/// If this member is present, eligible authenticators are filtered to only authenticators attached with the specified §5.4.5 Authenticator Attachment enumeration (enum AuthenticatorAttachment).
190+
/// If this member is present, eligible authenticators are filtered to only authenticators attached with the specified §5.4.5 Authenticator Attachment Enumeration (enum AuthenticatorAttachment).
190191
/// </summary>
191192
[JsonPropertyName("authenticatorAttachment")]
192193
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
193194
public AuthenticatorAttachment? AuthenticatorAttachment { get; set; }
194195

195196
private ResidentKeyRequirement _residentKey;
197+
196198
/// <summary>
197-
/// Specifies the extent to which the Relying Party desires to create a client-side discoverable credential. For historical reasons the naming retains the deprecated “resident” terminology. The value SHOULD be a member of ResidentKeyRequirement but client platforms MUST ignore unknown values, treating an unknown value as if the member does not exist. If no value is given then the effective value is required if requireResidentKey is true or discouraged if it is false or absent.
199+
/// Specifies the extent to which the Relying Party desires to create a client-side discoverable credential.
200+
/// For historical reasons the naming retains the deprecated “resident” terminology.
201+
/// The value SHOULD be a member of ResidentKeyRequirement but client platforms MUST ignore unknown values,
202+
/// treating an unknown value as if the member does not exist.
203+
/// If no value is given then the effective value is required if requireResidentKey is true or discouraged if it is false or absent.
198204
/// </summary>
199205
[JsonPropertyName("residentKey")]
200206
public ResidentKeyRequirement ResidentKey
@@ -207,12 +213,13 @@ public ResidentKeyRequirement ResidentKey
207213
{
208214
ResidentKeyRequirement.Required => true,
209215
ResidentKeyRequirement.Preferred or ResidentKeyRequirement.Discouraged => false,
210-
_ => throw new NotImplementedException(),
216+
_ => throw new NotImplementedException()
211217
};
212218
}
213219
}
214220

215221
private bool _requireResidentKey;
222+
216223
/// <summary>
217224
/// This member describes the Relying Parties' requirements regarding resident credentials. If the parameter is set to true, the authenticator MUST create a client-side-resident public key credential source when creating a public key credential.
218225
/// </summary>
@@ -245,20 +252,25 @@ public bool RequireResidentKey
245252
public class Fido2User
246253
{
247254
/// <summary>
248-
/// Required. A human-friendly identifier for a user account. It is intended only for display, i.e., aiding the user in determining the difference between user accounts with similar displayNames. For example, "alexm", "[email protected]" or "+14255551234". https://w3c.github.io/webauthn/#dictdef-publickeycredentialentity
255+
/// Required. A human-friendly identifier for a user account.
256+
/// It is intended only for display, i.e., aiding the user in determining the difference between user accounts with similar displayNames.
257+
/// For example, "alexm", "[email protected]" or "+14255551234". https://w3c.github.io/webauthn/#dictdef-publickeycredentialentity
249258
/// </summary>
250259
[JsonPropertyName("name")]
251260
public string Name { get; set; }
252261

253262
/// <summary>
254-
/// The user handle of the user account entity. To ensure secure operation, authentication and authorization decisions MUST be made on the basis of this id member, not the displayName nor name members
263+
/// The user handle of the user account entity.
264+
/// To ensure secure operation, authentication and authorization decisions MUST be made on the basis of this id member, not the displayName nor name members
255265
/// </summary>
256266
[JsonPropertyName("id")]
257267
[JsonConverter(typeof(Base64UrlConverter))]
258268
public byte[] Id { get; set; }
259269

260270
/// <summary>
261-
/// A human-friendly name for the user account, intended only for display. For example, "Alex P. Müller" or "田中 倫". The Relying Party SHOULD let the user choose this, and SHOULD NOT restrict the choice more than necessary.
271+
/// A human-friendly name for the user account, intended only for display.
272+
/// For example, "Alex P. Müller" or "田中 倫".
273+
/// The Relying Party SHOULD let the user choose this, and SHOULD NOT restrict the choice more than necessary.
262274
/// </summary>
263275
[JsonPropertyName("displayName")]
264276
public string DisplayName { get; set; }

Src/Fido2.Models/Objects/AttestationConveyancePreference.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
namespace Fido2NetLib.Objects;
55

66
/// <summary>
7-
/// AttestationConveyancePreference.
8-
/// https://w3c.github.io/webauthn/#attestation-convey
7+
/// AttestationConveyancePreference
8+
/// https://www.w3.org/TR/webauthn-2/#enum-attestation-convey
99
/// </summary>
1010
[JsonConverter(typeof(FidoEnumConverter<AttestationConveyancePreference>))]
1111
public enum AttestationConveyancePreference

Src/Fido2.Models/Objects/AuthenticatorAttachment.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Fido2NetLib.Objects;
1010
/// </summary>
1111
/// <remarks>
1212
/// Note: An authenticator attachment modality selection option is available only in the [[Create]](origin, options, sameOriginWithAncestors) operation. The Relying Party may use it to, for example, ensure the user has a roaming credential for authenticating on another client device; or to specifically register a platform credential for easier reauthentication using a particular client device. The [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) operation has no authenticator attachment modality selection option, so the Relying Party SHOULD accept any of the user’s registered credentials. The client and user will then use whichever is available and convenient at the time.
13-
/// https://w3c.github.io/webauthn/#attachment
13+
/// https://www.w3.org/TR/webauthn-2/#enum-attachment
1414
/// </remarks>
1515
[JsonConverter(typeof(FidoEnumConverter<AuthenticatorAttachment>))]
1616
public enum AuthenticatorAttachment

Src/Fido2.Models/Objects/AuthenticatorTransport.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
namespace Fido2NetLib.Objects;
55

66
/// <summary>
7-
/// Authenticators may implement various transports for communicating with clients. This enumeration defines hints as to how clients might communicate with a particular authenticator in order to obtain an assertion for a specific credential. Note that these hints represent the WebAuthn Relying Party's best belief as to how an authenticator may be reached. A Relying Party may obtain a list of transports hints from some attestation statement formats or via some out-of-band mechanism; it is outside the scope of this specification to define that mechanism.
8-
/// https://w3c.github.io/webauthn/#transport
7+
/// Authenticators may implement various transports for communicating with clients.
8+
/// This enumeration defines hints as to how clients might communicate with a particular
9+
/// authenticator in order to obtain an assertion for a specific credential.
10+
/// Note that these hints represent the WebAuthn Relying Party's best belief as to how an authenticator may be reached.
11+
/// A Relying Party will typically learn of the supported transports for a public key credential via getTransports().
12+
/// https://www.w3.org/TR/webauthn-2/#enum-transport
913
/// </summary>
1014
[JsonConverter(typeof(FidoEnumConverter<AuthenticatorTransport>))]
1115
public enum AuthenticatorTransport
@@ -23,13 +27,14 @@ public enum AuthenticatorTransport
2327
Nfc,
2428

2529
/// <summary>
26-
/// Indicates the respective authenticator can be contacted over Bluetooth Smart(Bluetooth Low Energy / BLE)
30+
/// Indicates the respective authenticator can be contacted over Bluetooth Smart (Bluetooth Low Energy / BLE).
2731
/// </summary>
2832
[EnumMember(Value = "ble")]
2933
Ble,
3034

3135
/// <summary>
32-
/// Indicates the respective authenticator is contacted using a client device-specific transport.These authenticators are not removable from the client device.
36+
/// Indicates the respective authenticator is contacted using a client device-specific transport, i.e., it is a platform authenticator.
37+
/// These authenticators are not removable from the client device.
3338
/// </summary>
3439
[EnumMember(Value = "internal")]
3540
Internal,

Src/Fido2.Models/Objects/PublicKeyCredentialType.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace Fido2NetLib.Objects;
55

66
/// <summary>
77
/// PublicKeyCredentialType.
8-
/// https://w3c.github.io/webauthn/#enumdef-publickeycredentialtype
8+
/// https://www.w3.org/TR/webauthn-2/#enum-credentialType
99
/// </summary>
1010
[JsonConverter(typeof(FidoEnumConverter<PublicKeyCredentialType>))]
1111
public enum PublicKeyCredentialType

Src/Fido2.Models/Objects/ResidentKeyRequirement.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace Fido2NetLib.Objects;
55

66
/// <summary>
77
/// This enumeration’s values describe the Relying Party's requirements for client-side discoverable credentials (formerly known as resident credentials or resident keys).
8-
/// https://w3c.github.io/webauthn/#enum-residentKeyRequirement
8+
/// https://www.w3.org/TR/webauthn-2/#enum-residentKeyRequirement
99
/// </summary>
1010
[JsonConverter(typeof(FidoEnumConverter<ResidentKeyRequirement>))]
1111
public enum ResidentKeyRequirement

Src/Fido2.Models/Objects/UserVerificationRequirement.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,30 @@
44
namespace Fido2NetLib.Objects;
55

66
/// <summary>
7-
/// A WebAuthn Relying Party may require user verification for some of its operations but not for others, and may use this type to express its needs.
8-
/// https://w3c.github.io/webauthn/#enumdef-userverificationrequirement
7+
/// A WebAuthn Relying Party may require user verification for some of its operations but not for others,
8+
/// and may use this type to express its needs.
9+
/// https://www.w3.org/TR/webauthn-2/#enumdef-userverificationrequirement
910
/// </summary>
1011
[JsonConverter(typeof(FidoEnumConverter<UserVerificationRequirement>))]
1112
public enum UserVerificationRequirement
1213
{
1314
/// <summary>
14-
/// This value indicates that the Relying Party requires user verification for the operation and will fail the operation if the response does not have the UV flag set.
15+
/// This value indicates that the Relying Party requires user verification for the operation
16+
/// and will fail the operation if the response does not have the UV flag set.
1517
/// </summary>
1618
[EnumMember(Value = "required")]
1719
Required,
1820

1921
/// <summary>
20-
/// This value indicates that the Relying Party prefers user verification for the operation if possible, but will not fail the operation if the response does not have the UV flag set.
22+
/// This value indicates that the Relying Party prefers user verification for the operation if possible,
23+
/// but will not fail the operation if the response does not have the UV flag set.
2124
/// </summary>
2225
[EnumMember(Value = "preferred")]
2326
Preferred,
2427

2528
/// <summary>
26-
/// This value indicates that the Relying Party does not want user verification employed during the operation(e.g., in the interest of minimizing disruption to the user interaction flow).
29+
/// This value indicates that the Relying Party does not want user verification employed during the operation
30+
/// (e.g., in the interest of minimizing disruption to the user interaction flow).
2731
/// </summary>
2832
[EnumMember(Value = "discouraged")]
2933
Discouraged

Src/Fido2/Extensions/EnumExtensions.cs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,28 @@ public static class EnumExtensions
1010
/// </summary>
1111
/// <typeparam name="TEnum">The type of enum.</typeparam>
1212
/// <param name="value">The EnumMemberAttribute's value.</param>
13-
/// <param name="ignoreCase">ignores the case when comparing values.</param>
1413
/// <returns>TEnum.</returns>
1514
/// <exception cref="ArgumentException">No XmlEnumAttribute code exists for type " + typeof(TEnum).ToString() + " corresponding to value of " + value</exception>
16-
public static TEnum ToEnum<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum>(this string value, bool ignoreCase = true) where TEnum : struct, Enum
15+
public static TEnum ToEnum<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum>(this string value) where TEnum : struct, Enum
1716
{
18-
// Try to parse it normally on the first try
19-
if (Enum.TryParse<TEnum>(value, ignoreCase, out var result))
20-
return result;
21-
2217
// Try with value from EnumMemberAttribute
23-
if (EnumNameMapper<TEnum>.TryGetValue(value, ignoreCase, out result))
18+
if (EnumNameMapper<TEnum>.TryGetValue(value, out var result))
2419
{
2520
return result;
2621
}
2722

28-
throw new ArgumentException($"Value '{value}' is not a valid enum name of '{typeof(TEnum)}' ({nameof(ignoreCase)}={ignoreCase}). Valid values are: {string.Join(", ", EnumNameMapper<TEnum>.GetNames())}.");
23+
// Then check the enum
24+
if (Enum.TryParse(value, out result))
25+
return result;
26+
27+
throw new ArgumentException($"Value '{value}' is not a valid enum name of '{typeof(TEnum)}'. Valid values are: {string.Join(", ", EnumNameMapper<TEnum>.GetNames())}.");
2928
}
3029

3130
/// <summary>
3231
/// Gets the EnumMemberAttribute's value from the enum's value.
3332
/// </summary>
3433
/// <typeparam name="TEnum">The type of enum.</typeparam>
35-
/// <param name="value">The enum's value.</param>
34+
/// <param name="value">The enum's value</param>
3635
/// <returns>string.</returns>
3736
public static string ToEnumMemberValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum>(this TEnum value) where TEnum : struct, Enum
3837
{

Test/EnumExtensionTest.cs

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,29 @@
1-
using Fido2NetLib;
2-
using Fido2NetLib.Objects;
1+
using Fido2NetLib.Objects;
32

4-
namespace fido2_net_lib.Test;
3+
namespace Fido2NetLib.Test;
54

65
public class EnumExtensionTest
76
{
87
[Fact]
98
public void TestToEnum()
109
{
11-
var enumNames = Enum.GetNames(typeof(AttestationConveyancePreference));
12-
foreach (var enumName in enumNames)
10+
foreach (var enumName in Enum.GetNames(typeof(AttestationConveyancePreference)))
1311
{
1412
enumName.ToEnum<AttestationConveyancePreference>();
1513
}
1614
}
1715

1816
[Theory]
19-
// ignoreCase true, valid
20-
[InlineData("INDIRECT", true, false)]
21-
[InlineData("indIrEcT", true, false)]
22-
[InlineData("indirect", true, false)]
23-
17+
// valid
18+
[InlineData("INDIRECT", false)]
19+
[InlineData("indIrEcT", false)]
20+
[InlineData("indirect", false)]
21+
[InlineData(nameof(AttestationConveyancePreference.Indirect), false)]
2422
// invalid
25-
[InlineData("Indirect_Invalid", true, true)]
26-
27-
// ignoreCase false, valid
28-
[InlineData(nameof(AttestationConveyancePreference.Indirect), false, false)]
29-
30-
// invalid
31-
[InlineData("Indirect_Invalid", false, true)]
32-
[InlineData("INDIRECT", false, true)]
33-
[InlineData("indIrEcT", false, true)]
34-
public void TestToEnumWithIgnoringCase(string value, bool ignoreCase, bool shouldThrow)
23+
[InlineData("Indirect_Invalid", true)]
24+
public void TestToEnumWithIgnoringCase(string value, bool shouldThrow)
3525
{
36-
var exception = Record.Exception(() => value.ToEnum<AttestationConveyancePreference>(ignoreCase));
26+
var exception = Record.Exception(() => value.ToEnum<AttestationConveyancePreference>());
3727

3828
if (shouldThrow)
3929
{
@@ -46,17 +36,17 @@ public void TestToEnumWithIgnoringCase(string value, bool ignoreCase, bool shoul
4636
}
4737

4838
[Theory]
49-
[InlineData("CROSS-PLATFORM", true, false)] // valid
50-
[InlineData("cRoss-PlatfoRm", true, false)] // valid
51-
[InlineData("cross-platform", true, false)] // valid
52-
[InlineData("cross_platform", true, true)] // invalid
53-
[InlineData("cross-platforms", true, true)] // invalid
54-
[InlineData("CROSS_PLATFORM", true, true)] // invalid
55-
[InlineData("CROSS-PLATFORM", false, true)] // invalid
56-
[InlineData("cRoss-PlatfoRm", false, true)] // invalid
57-
public void TestToEnumWithDashes(string value, bool ignoreCase, bool shouldThrow)
39+
// valid
40+
[InlineData("CROSS-PLATFORM", false)]
41+
[InlineData("cRoss-PlatfoRm", false)]
42+
[InlineData("cross-platform", false)]
43+
// invalid
44+
[InlineData("cross_platform", true)]
45+
[InlineData("cross-platforms", true)]
46+
[InlineData("CROSS_PLATFORM", true)]
47+
public void TestToEnumWithDashes(string value, bool shouldThrow)
5848
{
59-
var exception = Record.Exception(() => value.ToEnum<AuthenticatorAttachment>(ignoreCase));
49+
var exception = Record.Exception(() => value.ToEnum<AuthenticatorAttachment>());
6050

6151
if (shouldThrow)
6252
{

0 commit comments

Comments
 (0)