Allow multiple capabilities.

This commit is contained in:
Elias Bonnici 2024-01-26 11:59:37 +01:00
parent aee0824bf3
commit 81ac767036
No known key found for this signature in database
GPG Key ID: 5EAC28EA3F980CCF
10 changed files with 56 additions and 55 deletions

View File

@ -30,17 +30,23 @@ const _listEquality = ListEquality();
enum Availability { enabled, disabled, unsupported }
enum Application {
accounts,
webauthn,
fingerprints,
passkeys,
slots,
certificates,
openpgp,
hsmauth,
management;
accounts([Capability.oath]),
webauthn([Capability.u2f]),
fingerprints([Capability.fido2]),
passkeys([Capability.fido2]),
slots([Capability.otp]),
certificates([Capability.piv]),
openpgp([Capability.openpgp]),
hsmauth([Capability.hsmauth]),
management();
const Application();
final List<Capability> capabilities;
List<Capability> getCapabilities() {
return capabilities;
}
const Application([this.capabilities = const []]);
bool _inCapabilities(int capabilities) => switch (this) {
Application.accounts => Capability.oath.value & capabilities != 0,
@ -65,18 +71,6 @@ enum Application {
_ => name.substring(0, 1).toUpperCase() + name.substring(1),
};
Capability? getCapability() => switch (this) {
Application.accounts => Capability.oath,
Application.webauthn => Capability.u2f,
Application.passkeys => Capability.fido2,
Application.fingerprints => Capability.fido2,
Application.certificates => Capability.piv,
Application.slots => Capability.otp,
Application.hsmauth => Capability.hsmauth,
Application.openpgp => Capability.openpgp,
_ => null
};
Availability getAvailability(YubiKeyData data) {
if (this == Application.management) {
final version = data.info.version;

View File

@ -44,7 +44,7 @@ class AppPage extends StatelessWidget {
final Widget Function(BuildContext context)? actionButtonBuilder;
final Widget? fileDropOverlay;
final Function(File file)? onFileDropped;
final Capability? capability;
final List<Capability>? capabilities;
const AppPage({
super.key,
this.title,
@ -55,7 +55,7 @@ class AppPage extends StatelessWidget {
this.detailViewBuilder,
this.actionButtonBuilder,
this.fileDropOverlay,
this.capability,
this.capabilities,
this.onFileDropped,
this.delayedContent = false,
this.keyActionsBadge = false,
@ -169,7 +169,12 @@ class AppPage extends StatelessWidget {
.colorScheme
.primary
.withOpacity(0.9))),
if (capability != null) _CapabilityBadge(capability!)
if (capabilities != null)
Wrap(
spacing: 4.0,
runSpacing: 8.0,
children: [...capabilities!.map((c) => _CapabilityBadge(c))],
)
])
],
);

View File

@ -133,7 +133,7 @@ class MainPage extends ConsumerWidget {
return ref.watch(currentDeviceDataProvider).when(
data: (data) {
final app = ref.watch(currentAppProvider);
final capability = app.getCapability();
final capabilities = app.getCapabilities();
if (data.info.supportedCapabilities.isEmpty &&
data.name == 'Unrecognized device') {
return MessagePage(
@ -149,18 +149,20 @@ class MainPage extends ConsumerWidget {
Availability.unsupported) {
return MessagePage(
title: app.getDisplayName(l10n),
capability: capability,
capabilities: capabilities,
header: l10n.s_app_not_supported,
message: l10n.l_app_not_supported_on_yk(
capability?.getDisplayName(l10n) ?? app.name),
message: l10n.l_app_not_supported_on_yk(capabilities
.map((c) => c.getDisplayName(l10n))
.join(',')),
);
} else if (app.getAvailability(data) != Availability.enabled) {
return MessagePage(
title: app.getDisplayName(l10n),
capability: capability,
capabilities: capabilities,
header: l10n.s_app_disabled,
message: l10n.l_app_disabled_desc(
capability?.getDisplayName(l10n) ?? app.name),
message: l10n.l_app_disabled_desc(capabilities
.map((c) => c.getDisplayName(l10n))
.join(',')),
actions: [
ActionChip(
label: Text(data.info.version.major > 4

View File

@ -32,7 +32,7 @@ class MessagePage extends StatelessWidget {
final Widget Function(BuildContext context)? actionButtonBuilder;
final Widget? fileDropOverlay;
final Function(File file)? onFileDropped;
final Capability? capability;
final List<Capability>? capabilities;
final bool keyActionsBadge;
final bool centered;
@ -49,14 +49,14 @@ class MessagePage extends StatelessWidget {
this.onFileDropped,
this.delayedContent = false,
this.keyActionsBadge = false,
this.capability,
this.capabilities,
this.centered = false,
});
@override
Widget build(BuildContext context) => AppPage(
title: title,
capability: capability,
capabilities: capabilities,
centered: centered,
actions: actions,
keyActionsBuilder: keyActionsBuilder,

View File

@ -61,7 +61,7 @@ class FingerprintsScreen extends ConsumerWidget {
if (Capability.fido2.value & enabled == 0) {
return MessagePage(
title: l10n.s_fingerprints,
capability: Capability.fido2,
capabilities: const [Capability.fido2],
header: l10n.s_fido_disabled,
message: l10n.l_webauthn_req_fido2,
);
@ -105,7 +105,7 @@ class _FidoLockedPage extends ConsumerWidget {
)
],
title: l10n.s_fingerprints,
capability: Capability.fido2,
capabilities: const [Capability.fido2],
header: '${l10n.s_fingerprints_get_started} (1/2)',
message: l10n.p_set_fingerprints_desc,
keyActionsBuilder: hasActions ? _buildActions : null,
@ -116,7 +116,7 @@ class _FidoLockedPage extends ConsumerWidget {
if (state.forcePinChange) {
return MessagePage(
title: l10n.s_fingerprints,
capability: Capability.fido2,
capabilities: const [Capability.fido2],
header: l10n.s_pin_change_required,
message: l10n.l_pin_change_required_desc,
keyActionsBuilder: hasActions ? _buildActions : null,
@ -137,7 +137,7 @@ class _FidoLockedPage extends ConsumerWidget {
return AppPage(
title: l10n.s_fingerprints,
capability: Capability.fido2,
capabilities: const [Capability.fido2],
keyActionsBuilder: hasActions ? _buildActions : null,
builder: (context, _) => Column(
children: [
@ -190,7 +190,7 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> {
)
],
title: l10n.s_fingerprints,
capability: Capability.fido2,
capabilities: const [Capability.fido2],
header: '${l10n.s_fingerprints_get_started} (2/2)',
message: l10n.l_add_one_or_more_fps,
keyActionsBuilder: hasActions
@ -250,7 +250,7 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> {
},
builder: (context) => AppPage(
title: l10n.s_fingerprints,
capability: Capability.fido2,
capabilities: const [Capability.fido2],
detailViewBuilder: fingerprint != null
? (context) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,

View File

@ -61,7 +61,7 @@ class PasskeysScreen extends ConsumerWidget {
if (Capability.fido2.value & enabled == 0) {
return MessagePage(
title: l10n.s_passkeys,
capability: Capability.fido2,
capabilities: const [Capability.fido2],
header: l10n.s_fido_disabled,
message: l10n.l_webauthn_req_fido2,
);
@ -94,7 +94,7 @@ class _FidoLockedPage extends ConsumerWidget {
if (!state.hasPin) {
return MessagePage(
title: l10n.s_passkeys,
capability: Capability.fido2,
capabilities: const [Capability.fido2],
header: state.credMgmt
? l10n.l_no_discoverable_accounts
: l10n.l_ready_to_use,
@ -107,7 +107,7 @@ class _FidoLockedPage extends ConsumerWidget {
if (!state.credMgmt && state.bioEnroll == null) {
return MessagePage(
title: l10n.s_passkeys,
capability: Capability.fido2,
capabilities: const [Capability.fido2],
header: l10n.l_ready_to_use,
message: l10n.l_register_sk_on_websites,
keyActionsBuilder: hasActions ? _buildActions : null,
@ -129,7 +129,7 @@ class _FidoLockedPage extends ConsumerWidget {
)
],
title: l10n.s_passkeys,
capability: Capability.fido2,
capabilities: const [Capability.fido2],
header: l10n.s_pin_change_required,
message: l10n.l_pin_change_required_desc,
keyActionsBuilder: hasActions ? _buildActions : null,
@ -139,7 +139,7 @@ class _FidoLockedPage extends ConsumerWidget {
return AppPage(
title: l10n.s_passkeys,
capability: Capability.fido2,
capabilities: const [Capability.fido2],
keyActionsBuilder: hasActions ? _buildActions : null,
builder: (context, _) => Column(
children: [
@ -177,7 +177,7 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> {
// TODO: Special handling for credMgmt not supported
return MessagePage(
title: l10n.s_passkeys,
capability: Capability.fido2,
capabilities: const [Capability.fido2],
header: l10n.l_no_discoverable_accounts,
message: l10n.l_register_sk_on_websites,
keyActionsBuilder: hasActions
@ -197,7 +197,7 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> {
if (credentials.isEmpty) {
return MessagePage(
title: l10n.s_passkeys,
capability: Capability.fido2,
capabilities: const [Capability.fido2],
header: l10n.l_no_discoverable_accounts,
message: l10n.l_register_sk_on_websites,
keyActionsBuilder: hasActions
@ -246,7 +246,7 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> {
},
builder: (context) => AppPage(
title: l10n.s_passkeys,
capability: Capability.fido2,
capabilities: const [Capability.fido2],
detailViewBuilder: credential != null
? (context) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,

View File

@ -28,7 +28,7 @@ class WebAuthnScreen extends StatelessWidget {
final l10n = AppLocalizations.of(context)!;
return MessagePage(
title: l10n.s_webauthn,
capability: Capability.u2f,
capabilities: const [Capability.u2f],
header: l10n.l_ready_to_use,
message: l10n.l_register_sk_on_websites,
);

View File

@ -83,7 +83,7 @@ class _LockedView extends ConsumerWidget {
final hasActions = ref.watch(featureProvider)(features.actions);
return AppPage(
title: AppLocalizations.of(context)!.s_accounts,
capability: Capability.oath,
capabilities: const [Capability.oath],
keyActionsBuilder: hasActions
? (context) => oathBuildActions(context, devicePath, oathState, ref)
: null,
@ -175,7 +175,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
)
],
title: l10n.s_accounts,
capability: Capability.oath,
capabilities: const [Capability.oath],
key: keys.noAccountsView,
header: l10n.l_authenticator_get_started,
message: l10n.p_no_accounts_desc,
@ -249,7 +249,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
},
builder: (context) => AppPage(
title: l10n.s_accounts,
capability: Capability.oath,
capabilities: const [Capability.oath],
keyActionsBuilder: hasActions
? (context) => oathBuildActions(
context,

View File

@ -93,7 +93,7 @@ class _OtpScreenState extends ConsumerState<OtpScreen> {
},
child: AppPage(
title: l10n.s_slots,
capability: Capability.otp,
capabilities: const [Capability.otp],
detailViewBuilder: selected != null
? (context) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,

View File

@ -106,7 +106,7 @@ class _PivScreenState extends ConsumerState<PivScreen> {
},
child: AppPage(
title: l10n.s_certificates,
capability: Capability.piv,
capabilities: const [Capability.piv],
detailViewBuilder: selected != null
? (context) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,