mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-22 00:12:09 +03:00
Expose FIPS approved and ensure DeviceInfo is refreshed
This commit is contained in:
parent
0b7d6736cb
commit
903f96acc8
@ -9,9 +9,9 @@ part of 'models.dart';
|
||||
_$KeyCustomizationImpl _$$KeyCustomizationImplFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$KeyCustomizationImpl(
|
||||
serial: json['serial'] as int,
|
||||
serial: (json['serial'] as num).toInt(),
|
||||
name: json['name'] as String?,
|
||||
color: const _ColorConverter().fromJson(json['color'] as int?),
|
||||
color: const _ColorConverter().fromJson((json['color'] as num?)?.toInt()),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$KeyCustomizationImplToJson(
|
||||
|
@ -224,12 +224,9 @@ final _desktopDeviceDataProvider =
|
||||
ref.watch(rpcProvider).valueOrNull,
|
||||
ref.watch(currentDeviceProvider),
|
||||
);
|
||||
if (notifier._deviceNode is NfcReaderNode) {
|
||||
// If this is an NFC reader, listen on WindowState.
|
||||
ref.listen<WindowState>(windowStateProvider, (_, windowState) {
|
||||
notifier._notifyWindowState(windowState);
|
||||
}, fireImmediately: true);
|
||||
}
|
||||
ref.listen<WindowState>(windowStateProvider, (_, windowState) {
|
||||
notifier._notifyWindowState(windowState);
|
||||
}, fireImmediately: true);
|
||||
return notifier;
|
||||
});
|
||||
|
||||
@ -259,7 +256,11 @@ class CurrentDeviceDataNotifier extends StateNotifier<AsyncValue<YubiKeyData>> {
|
||||
|
||||
void _notifyWindowState(WindowState windowState) {
|
||||
if (windowState.active) {
|
||||
_pollCard();
|
||||
if (_deviceNode is UsbYubiKeyNode?) {
|
||||
_pollUsb();
|
||||
} else {
|
||||
_pollCard();
|
||||
}
|
||||
} else {
|
||||
_pollTimer?.cancel();
|
||||
// TODO: Should we clear the key here?
|
||||
@ -275,11 +276,27 @@ class CurrentDeviceDataNotifier extends StateNotifier<AsyncValue<YubiKeyData>> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _pollUsb() async {
|
||||
_pollTimer?.cancel();
|
||||
final node = _deviceNode!;
|
||||
var result = await _rpc?.command('get', node.path.segments);
|
||||
if (mounted && result != null) {
|
||||
final newState = YubiKeyData(node, result['data']['name'],
|
||||
DeviceInfo.fromJson(result['data']['info']));
|
||||
if (state.valueOrNull != newState) {
|
||||
_log.info('Configuration change in current USB device');
|
||||
state = AsyncValue.data(newState);
|
||||
}
|
||||
}
|
||||
if (mounted) {
|
||||
_pollTimer = Timer(_usbPollDelay, _pollUsb);
|
||||
}
|
||||
}
|
||||
|
||||
void _pollCard() async {
|
||||
_pollTimer?.cancel();
|
||||
final node = _deviceNode!;
|
||||
try {
|
||||
_log.debug('Polling for NFC device changes...');
|
||||
var result = await _rpc?.command('get', node.path.segments);
|
||||
if (mounted && result != null) {
|
||||
if (result['data']['present']) {
|
||||
@ -289,9 +306,8 @@ class CurrentDeviceDataNotifier extends StateNotifier<AsyncValue<YubiKeyData>> {
|
||||
if (oldState != null && oldState != newState) {
|
||||
// Ensure state is cleared
|
||||
state = const AsyncValue.loading();
|
||||
} else {
|
||||
state = AsyncValue.data(newState);
|
||||
}
|
||||
state = AsyncValue.data(newState);
|
||||
} else {
|
||||
final status = result['data']['status'];
|
||||
// Only update if status is not changed
|
||||
|
@ -10,7 +10,7 @@ _$FidoStateImpl _$$FidoStateImplFromJson(Map<String, dynamic> json) =>
|
||||
_$FidoStateImpl(
|
||||
info: json['info'] as Map<String, dynamic>,
|
||||
unlocked: json['unlocked'] as bool,
|
||||
pinRetries: json['pin_retries'] as int?,
|
||||
pinRetries: (json['pin_retries'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$FidoStateImplToJson(_$FidoStateImpl instance) =>
|
||||
|
@ -78,6 +78,8 @@ class DeviceConfig with _$DeviceConfig {
|
||||
|
||||
@freezed
|
||||
class DeviceInfo with _$DeviceInfo {
|
||||
const DeviceInfo._(); // Added constructor
|
||||
|
||||
factory DeviceInfo(
|
||||
DeviceConfig config,
|
||||
int? serial,
|
||||
@ -88,8 +90,16 @@ class DeviceInfo with _$DeviceInfo {
|
||||
bool isFips,
|
||||
bool isSky,
|
||||
bool pinComplexity,
|
||||
int fipsCapable) = _DeviceInfo;
|
||||
int fipsCapable,
|
||||
int fipsApproved) = _DeviceInfo;
|
||||
|
||||
factory DeviceInfo.fromJson(Map<String, dynamic> json) =>
|
||||
_$DeviceInfoFromJson(json);
|
||||
|
||||
/// Gets the tuple fipsCapable, fipsApproved for the given capability.
|
||||
(bool fipsCapable, bool fipsApproved) getFipsStatus(Capability capability) {
|
||||
final capable = fipsCapable & Capability.oath.value != 0;
|
||||
final approved = capable && fipsApproved & Capability.oath.value != 0;
|
||||
return (capable, approved);
|
||||
}
|
||||
}
|
||||
|
@ -247,6 +247,7 @@ mixin _$DeviceInfo {
|
||||
bool get isSky => throw _privateConstructorUsedError;
|
||||
bool get pinComplexity => throw _privateConstructorUsedError;
|
||||
int get fipsCapable => throw _privateConstructorUsedError;
|
||||
int get fipsApproved => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
@ -270,7 +271,8 @@ abstract class $DeviceInfoCopyWith<$Res> {
|
||||
bool isFips,
|
||||
bool isSky,
|
||||
bool pinComplexity,
|
||||
int fipsCapable});
|
||||
int fipsCapable,
|
||||
int fipsApproved});
|
||||
|
||||
$DeviceConfigCopyWith<$Res> get config;
|
||||
$VersionCopyWith<$Res> get version;
|
||||
@ -299,6 +301,7 @@ class _$DeviceInfoCopyWithImpl<$Res, $Val extends DeviceInfo>
|
||||
Object? isSky = null,
|
||||
Object? pinComplexity = null,
|
||||
Object? fipsCapable = null,
|
||||
Object? fipsApproved = null,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
config: null == config
|
||||
@ -341,6 +344,10 @@ class _$DeviceInfoCopyWithImpl<$Res, $Val extends DeviceInfo>
|
||||
? _value.fipsCapable
|
||||
: fipsCapable // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
fipsApproved: null == fipsApproved
|
||||
? _value.fipsApproved
|
||||
: fipsApproved // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
) as $Val);
|
||||
}
|
||||
|
||||
@ -379,7 +386,8 @@ abstract class _$$DeviceInfoImplCopyWith<$Res>
|
||||
bool isFips,
|
||||
bool isSky,
|
||||
bool pinComplexity,
|
||||
int fipsCapable});
|
||||
int fipsCapable,
|
||||
int fipsApproved});
|
||||
|
||||
@override
|
||||
$DeviceConfigCopyWith<$Res> get config;
|
||||
@ -408,6 +416,7 @@ class __$$DeviceInfoImplCopyWithImpl<$Res>
|
||||
Object? isSky = null,
|
||||
Object? pinComplexity = null,
|
||||
Object? fipsCapable = null,
|
||||
Object? fipsApproved = null,
|
||||
}) {
|
||||
return _then(_$DeviceInfoImpl(
|
||||
null == config
|
||||
@ -450,13 +459,17 @@ class __$$DeviceInfoImplCopyWithImpl<$Res>
|
||||
? _value.fipsCapable
|
||||
: fipsCapable // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
null == fipsApproved
|
||||
? _value.fipsApproved
|
||||
: fipsApproved // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$DeviceInfoImpl implements _DeviceInfo {
|
||||
class _$DeviceInfoImpl extends _DeviceInfo {
|
||||
_$DeviceInfoImpl(
|
||||
this.config,
|
||||
this.serial,
|
||||
@ -467,8 +480,10 @@ class _$DeviceInfoImpl implements _DeviceInfo {
|
||||
this.isFips,
|
||||
this.isSky,
|
||||
this.pinComplexity,
|
||||
this.fipsCapable)
|
||||
: _supportedCapabilities = supportedCapabilities;
|
||||
this.fipsCapable,
|
||||
this.fipsApproved)
|
||||
: _supportedCapabilities = supportedCapabilities,
|
||||
super._();
|
||||
|
||||
factory _$DeviceInfoImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$DeviceInfoImplFromJson(json);
|
||||
@ -500,10 +515,12 @@ class _$DeviceInfoImpl implements _DeviceInfo {
|
||||
final bool pinComplexity;
|
||||
@override
|
||||
final int fipsCapable;
|
||||
@override
|
||||
final int fipsApproved;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'DeviceInfo(config: $config, serial: $serial, version: $version, formFactor: $formFactor, supportedCapabilities: $supportedCapabilities, isLocked: $isLocked, isFips: $isFips, isSky: $isSky, pinComplexity: $pinComplexity, fipsCapable: $fipsCapable)';
|
||||
return 'DeviceInfo(config: $config, serial: $serial, version: $version, formFactor: $formFactor, supportedCapabilities: $supportedCapabilities, isLocked: $isLocked, isFips: $isFips, isSky: $isSky, pinComplexity: $pinComplexity, fipsCapable: $fipsCapable, fipsApproved: $fipsApproved)';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -525,7 +542,9 @@ class _$DeviceInfoImpl implements _DeviceInfo {
|
||||
(identical(other.pinComplexity, pinComplexity) ||
|
||||
other.pinComplexity == pinComplexity) &&
|
||||
(identical(other.fipsCapable, fipsCapable) ||
|
||||
other.fipsCapable == fipsCapable));
|
||||
other.fipsCapable == fipsCapable) &&
|
||||
(identical(other.fipsApproved, fipsApproved) ||
|
||||
other.fipsApproved == fipsApproved));
|
||||
}
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@ -541,7 +560,8 @@ class _$DeviceInfoImpl implements _DeviceInfo {
|
||||
isFips,
|
||||
isSky,
|
||||
pinComplexity,
|
||||
fipsCapable);
|
||||
fipsCapable,
|
||||
fipsApproved);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
@ -557,7 +577,7 @@ class _$DeviceInfoImpl implements _DeviceInfo {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _DeviceInfo implements DeviceInfo {
|
||||
abstract class _DeviceInfo extends DeviceInfo {
|
||||
factory _DeviceInfo(
|
||||
final DeviceConfig config,
|
||||
final int? serial,
|
||||
@ -568,7 +588,9 @@ abstract class _DeviceInfo implements DeviceInfo {
|
||||
final bool isFips,
|
||||
final bool isSky,
|
||||
final bool pinComplexity,
|
||||
final int fipsCapable) = _$DeviceInfoImpl;
|
||||
final int fipsCapable,
|
||||
final int fipsApproved) = _$DeviceInfoImpl;
|
||||
_DeviceInfo._() : super._();
|
||||
|
||||
factory _DeviceInfo.fromJson(Map<String, dynamic> json) =
|
||||
_$DeviceInfoImpl.fromJson;
|
||||
@ -594,6 +616,8 @@ abstract class _DeviceInfo implements DeviceInfo {
|
||||
@override
|
||||
int get fipsCapable;
|
||||
@override
|
||||
int get fipsApproved;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$$DeviceInfoImplCopyWith<_$DeviceInfoImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
|
@ -46,6 +46,7 @@ _$DeviceInfoImpl _$$DeviceInfoImplFromJson(Map<String, dynamic> json) =>
|
||||
json['is_sky'] as bool,
|
||||
json['pin_complexity'] as bool,
|
||||
(json['fips_capable'] as num).toInt(),
|
||||
(json['fips_approved'] as num).toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$DeviceInfoImplToJson(_$DeviceInfoImpl instance) =>
|
||||
@ -61,6 +62,7 @@ Map<String, dynamic> _$$DeviceInfoImplToJson(_$DeviceInfoImpl instance) =>
|
||||
'is_sky': instance.isSky,
|
||||
'pin_complexity': instance.pinComplexity,
|
||||
'fips_capable': instance.fipsCapable,
|
||||
'fips_approved': instance.fipsApproved,
|
||||
};
|
||||
|
||||
const _$FormFactorEnumMap = {
|
||||
|
@ -13,7 +13,7 @@ _$OathCredentialImpl _$$OathCredentialImplFromJson(Map<String, dynamic> json) =>
|
||||
const _IssuerConverter().fromJson(json['issuer'] as String?),
|
||||
json['name'] as String,
|
||||
$enumDecode(_$OathTypeEnumMap, json['oath_type']),
|
||||
json['period'] as int,
|
||||
(json['period'] as num).toInt(),
|
||||
json['touch_required'] as bool,
|
||||
);
|
||||
|
||||
@ -37,8 +37,8 @@ const _$OathTypeEnumMap = {
|
||||
_$OathCodeImpl _$$OathCodeImplFromJson(Map<String, dynamic> json) =>
|
||||
_$OathCodeImpl(
|
||||
json['value'] as String,
|
||||
json['valid_from'] as int,
|
||||
json['valid_to'] as int,
|
||||
(json['valid_from'] as num).toInt(),
|
||||
(json['valid_to'] as num).toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$OathCodeImplToJson(_$OathCodeImpl instance) =>
|
||||
@ -98,9 +98,9 @@ _$CredentialDataImpl _$$CredentialDataImplFromJson(Map<String, dynamic> json) =>
|
||||
hashAlgorithm:
|
||||
$enumDecodeNullable(_$HashAlgorithmEnumMap, json['hash_algorithm']) ??
|
||||
defaultHashAlgorithm,
|
||||
digits: json['digits'] as int? ?? defaultDigits,
|
||||
period: json['period'] as int? ?? defaultPeriod,
|
||||
counter: json['counter'] as int? ?? defaultCounter,
|
||||
digits: (json['digits'] as num?)?.toInt() ?? defaultDigits,
|
||||
period: (json['period'] as num?)?.toInt() ?? defaultPeriod,
|
||||
counter: (json['counter'] as num?)?.toInt() ?? defaultCounter,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$CredentialDataImplToJson(
|
||||
|
@ -22,6 +22,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
||||
import '../../app/message.dart';
|
||||
import '../../app/models.dart';
|
||||
import '../../app/state.dart';
|
||||
import '../../management/models.dart';
|
||||
import '../../widgets/app_input_decoration.dart';
|
||||
import '../../widgets/app_text_field.dart';
|
||||
import '../../widgets/focus_utils.dart';
|
||||
@ -82,6 +83,9 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final fipsCapable = ref.watch(currentDeviceDataProvider).maybeWhen(
|
||||
data: (data) => data.info.getFipsStatus(Capability.oath).$1,
|
||||
orElse: () => false);
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final isValid = !_currentIsWrong &&
|
||||
_newPassword.isNotEmpty &&
|
||||
@ -142,37 +146,40 @@ class _ManagePasswordDialogState extends ConsumerState<ManagePasswordDialog> {
|
||||
spacing: 4.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
key: keys.removePasswordButton,
|
||||
onPressed: _currentPasswordController.text.isNotEmpty &&
|
||||
!_currentIsWrong
|
||||
? () async {
|
||||
final result = await ref
|
||||
.read(oathStateProvider(widget.path).notifier)
|
||||
.unsetPassword(_currentPasswordController.text);
|
||||
if (result) {
|
||||
if (mounted) {
|
||||
await ref.read(withContextProvider)(
|
||||
(context) async {
|
||||
Navigator.of(context).pop();
|
||||
showMessage(context, l10n.s_password_removed);
|
||||
if (!fipsCapable)
|
||||
OutlinedButton(
|
||||
key: keys.removePasswordButton,
|
||||
onPressed: _currentPasswordController.text.isNotEmpty &&
|
||||
!_currentIsWrong
|
||||
? () async {
|
||||
final result = await ref
|
||||
.read(oathStateProvider(widget.path).notifier)
|
||||
.unsetPassword(
|
||||
_currentPasswordController.text);
|
||||
if (result) {
|
||||
if (mounted) {
|
||||
await ref.read(withContextProvider)(
|
||||
(context) async {
|
||||
Navigator.of(context).pop();
|
||||
showMessage(
|
||||
context, l10n.s_password_removed);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
_currentPasswordController.selection =
|
||||
TextSelection(
|
||||
baseOffset: 0,
|
||||
extentOffset: _currentPasswordController
|
||||
.text.length);
|
||||
_currentPasswordFocus.requestFocus();
|
||||
setState(() {
|
||||
_currentIsWrong = true;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
_currentPasswordController.selection =
|
||||
TextSelection(
|
||||
baseOffset: 0,
|
||||
extentOffset: _currentPasswordController
|
||||
.text.length);
|
||||
_currentPasswordFocus.requestFocus();
|
||||
setState(() {
|
||||
_currentIsWrong = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
: null,
|
||||
child: Text(l10n.s_remove_password),
|
||||
),
|
||||
: null,
|
||||
child: Text(l10n.s_remove_password),
|
||||
),
|
||||
if (widget.state.remembered)
|
||||
OutlinedButton(
|
||||
child: Text(l10n.s_clear_saved_password),
|
||||
|
@ -9,8 +9,8 @@ part of 'models.dart';
|
||||
_$PinMetadataImpl _$$PinMetadataImplFromJson(Map<String, dynamic> json) =>
|
||||
_$PinMetadataImpl(
|
||||
json['default_value'] as bool,
|
||||
json['total_attempts'] as int,
|
||||
json['attempts_remaining'] as int,
|
||||
(json['total_attempts'] as num).toInt(),
|
||||
(json['attempts_remaining'] as num).toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$PinMetadataImplToJson(_$PinMetadataImpl instance) =>
|
||||
@ -113,7 +113,7 @@ _$PivStateImpl _$$PivStateImplFromJson(Map<String, dynamic> json) =>
|
||||
authenticated: json['authenticated'] as bool,
|
||||
derivedKey: json['derived_key'] as bool,
|
||||
storedKey: json['stored_key'] as bool,
|
||||
pinAttempts: json['pin_attempts'] as int,
|
||||
pinAttempts: (json['pin_attempts'] as num).toInt(),
|
||||
chuid: json['chuid'] as String?,
|
||||
ccc: json['ccc'] as String?,
|
||||
metadata: json['metadata'] == null
|
||||
@ -157,7 +157,7 @@ Map<String, dynamic> _$$CertInfoImplToJson(_$CertInfoImpl instance) =>
|
||||
|
||||
_$PivSlotImpl _$$PivSlotImplFromJson(Map<String, dynamic> json) =>
|
||||
_$PivSlotImpl(
|
||||
slot: SlotId.fromJson(json['slot'] as int),
|
||||
slot: SlotId.fromJson((json['slot'] as num).toInt()),
|
||||
metadata: json['metadata'] == null
|
||||
? null
|
||||
: SlotMetadata.fromJson(json['metadata'] as Map<String, dynamic>),
|
||||
|
@ -165,8 +165,8 @@ class _ManagePinPukDialogState extends ConsumerState<ManagePinPukDialog> {
|
||||
final isBio = [FormFactor.usbABio, FormFactor.usbCBio]
|
||||
.contains(deviceData?.info.formFactor);
|
||||
|
||||
final fipsCapable = deviceData?.info.fipsCapable ?? 0;
|
||||
final isFipsCapable = fipsCapable & Capability.piv.value != 0;
|
||||
final isFipsCapable =
|
||||
deviceData?.info.getFipsStatus(Capability.piv).$1 ?? false;
|
||||
|
||||
// Old YubiKeys allowed a 4 digit PIN
|
||||
final currentMinPinLen = isFipsCapable
|
||||
|
Loading…
Reference in New Issue
Block a user