mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-23 10:11:52 +03:00
Merge PR #1491
This commit is contained in:
commit
de99f6bd32
@ -112,6 +112,8 @@ class FidoManager(
|
|||||||
|
|
||||||
private val pinStore = FidoPinStore()
|
private val pinStore = FidoPinStore()
|
||||||
|
|
||||||
|
private var pinRetries : Int? = null
|
||||||
|
|
||||||
private val resetHelper =
|
private val resetHelper =
|
||||||
FidoResetHelper(deviceManager, fidoViewModel, mainViewModel, connectionHelper, pinStore)
|
FidoResetHelper(deviceManager, fidoViewModel, mainViewModel, connectionHelper, pinStore)
|
||||||
|
|
||||||
@ -124,6 +126,8 @@ class FidoManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
pinRetries = null
|
||||||
|
|
||||||
deviceManager.addDeviceListener(this)
|
deviceManager.addDeviceListener(this)
|
||||||
|
|
||||||
fidoChannel.setHandler(coroutineScope) { method, args ->
|
fidoChannel.setHandler(coroutineScope) { method, args ->
|
||||||
@ -239,10 +243,14 @@ class FidoManager(
|
|||||||
connectionHelper.cancelPending()
|
connectionHelper.cancelPending()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val infoData = fidoSession.cachedInfo
|
||||||
|
val clientPin =
|
||||||
|
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(infoData))
|
||||||
|
|
||||||
|
pinRetries = if (infoData.options["clientPin"] == true) clientPin.pinRetries.count else null
|
||||||
|
|
||||||
fidoViewModel.setSessionState(
|
fidoViewModel.setSessionState(
|
||||||
Session(
|
Session(infoData, pinStore.hasPin(), pinRetries)
|
||||||
fidoSession.cachedInfo, pinStore.hasPin()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Update deviceInfo since the deviceId has changed
|
// Update deviceInfo since the deviceId has changed
|
||||||
@ -275,8 +283,6 @@ class FidoManager(
|
|||||||
pin: CharArray
|
pin: CharArray
|
||||||
): String {
|
): String {
|
||||||
|
|
||||||
//fidoViewModel.setSessionLoadingState()
|
|
||||||
|
|
||||||
val pinPermissionsCM = getPinPermissionsCM(fidoSession)
|
val pinPermissionsCM = getPinPermissionsCM(fidoSession)
|
||||||
val pinPermissionsBE = getPinPermissionsBE(fidoSession)
|
val pinPermissionsBE = getPinPermissionsBE(fidoSession)
|
||||||
val permissions = pinPermissionsCM or pinPermissionsBE
|
val permissions = pinPermissionsCM or pinPermissionsBE
|
||||||
@ -298,16 +304,23 @@ class FidoManager(
|
|||||||
|
|
||||||
pinStore.setPin(pin)
|
pinStore.setPin(pin)
|
||||||
|
|
||||||
|
pinRetries = clientPin.pinRetries.count
|
||||||
|
|
||||||
fidoViewModel.setSessionState(
|
fidoViewModel.setSessionState(
|
||||||
Session(
|
Session(
|
||||||
fidoSession.info,
|
fidoSession.info,
|
||||||
pinStore.hasPin()
|
pinStore.hasPin(),
|
||||||
|
pinRetries
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return JSONObject(mapOf("success" to true)).toString()
|
return JSONObject(mapOf("success" to true)).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun catchPinErrors(clientPin: ClientPin, block: () -> String): String =
|
private fun catchPinErrors(
|
||||||
|
fidoSession: YubiKitFidoSession,
|
||||||
|
clientPin: ClientPin,
|
||||||
|
block: () -> String
|
||||||
|
): String =
|
||||||
try {
|
try {
|
||||||
block()
|
block()
|
||||||
} catch (ctapException: CtapException) {
|
} catch (ctapException: CtapException) {
|
||||||
@ -318,6 +331,15 @@ class FidoManager(
|
|||||||
) {
|
) {
|
||||||
pinStore.setPin(null)
|
pinStore.setPin(null)
|
||||||
fidoViewModel.updateCredentials(emptyList())
|
fidoViewModel.updateCredentials(emptyList())
|
||||||
|
pinRetries = clientPin.pinRetries.count
|
||||||
|
|
||||||
|
fidoViewModel.setSessionState(
|
||||||
|
Session(
|
||||||
|
fidoSession.info,
|
||||||
|
pinStore.hasPin(),
|
||||||
|
pinRetries
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if (ctapException.ctapError == CtapException.ERR_PIN_POLICY_VIOLATION) {
|
if (ctapException.ctapError == CtapException.ERR_PIN_POLICY_VIOLATION) {
|
||||||
JSONObject(
|
JSONObject(
|
||||||
@ -330,7 +352,7 @@ class FidoManager(
|
|||||||
JSONObject(
|
JSONObject(
|
||||||
mapOf(
|
mapOf(
|
||||||
"success" to false,
|
"success" to false,
|
||||||
"pinRetries" to clientPin.pinRetries.count,
|
"pinRetries" to pinRetries,
|
||||||
"authBlocked" to (ctapException.ctapError == CtapException.ERR_PIN_AUTH_BLOCKED),
|
"authBlocked" to (ctapException.ctapError == CtapException.ERR_PIN_AUTH_BLOCKED),
|
||||||
)
|
)
|
||||||
).toString()
|
).toString()
|
||||||
@ -347,7 +369,7 @@ class FidoManager(
|
|||||||
val clientPin =
|
val clientPin =
|
||||||
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
|
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
|
||||||
|
|
||||||
catchPinErrors(clientPin) {
|
catchPinErrors(fidoSession, clientPin) {
|
||||||
unlockSession(fidoSession, clientPin, pin)
|
unlockSession(fidoSession, clientPin, pin)
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
@ -383,7 +405,7 @@ class FidoManager(
|
|||||||
val clientPin =
|
val clientPin =
|
||||||
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
|
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
|
||||||
|
|
||||||
catchPinErrors(clientPin) {
|
catchPinErrors(fidoSession, clientPin) {
|
||||||
setOrChangePin(fidoSession, clientPin, pin, newPin)
|
setOrChangePin(fidoSession, clientPin, pin, newPin)
|
||||||
unlockSession(fidoSession, clientPin, newPin)
|
unlockSession(fidoSession, clientPin, newPin)
|
||||||
}
|
}
|
||||||
@ -489,7 +511,7 @@ class FidoManager(
|
|||||||
val bioEnrollment = FingerprintBioEnrollment(fidoSession, clientPin.pinUvAuth, token)
|
val bioEnrollment = FingerprintBioEnrollment(fidoSession, clientPin.pinUvAuth, token)
|
||||||
bioEnrollment.removeEnrollment(HexCodec.hexStringToBytes(templateId))
|
bioEnrollment.removeEnrollment(HexCodec.hexStringToBytes(templateId))
|
||||||
fidoViewModel.removeFingerprint(templateId)
|
fidoViewModel.removeFingerprint(templateId)
|
||||||
fidoViewModel.setSessionState(Session(fidoSession.info, pinStore.hasPin()))
|
fidoViewModel.setSessionState(Session(fidoSession.info, pinStore.hasPin(), pinRetries))
|
||||||
return@useSession JSONObject(
|
return@useSession JSONObject(
|
||||||
mapOf(
|
mapOf(
|
||||||
"success" to true,
|
"success" to true,
|
||||||
@ -513,7 +535,7 @@ class FidoManager(
|
|||||||
val bioEnrollment = FingerprintBioEnrollment(fidoSession, clientPin.pinUvAuth, token)
|
val bioEnrollment = FingerprintBioEnrollment(fidoSession, clientPin.pinUvAuth, token)
|
||||||
bioEnrollment.setName(HexCodec.hexStringToBytes(templateId), name)
|
bioEnrollment.setName(HexCodec.hexStringToBytes(templateId), name)
|
||||||
fidoViewModel.renameFingerprint(templateId, name)
|
fidoViewModel.renameFingerprint(templateId, name)
|
||||||
fidoViewModel.setSessionState(Session(fidoSession.info, pinStore.hasPin()))
|
fidoViewModel.setSessionState(Session(fidoSession.info, pinStore.hasPin(), pinRetries))
|
||||||
return@useSession JSONObject(
|
return@useSession JSONObject(
|
||||||
mapOf(
|
mapOf(
|
||||||
"success" to true,
|
"success" to true,
|
||||||
@ -592,7 +614,7 @@ class FidoManager(
|
|||||||
|
|
||||||
val templateIdHexString = HexCodec.bytesToHexString(templateId)
|
val templateIdHexString = HexCodec.bytesToHexString(templateId)
|
||||||
fidoViewModel.addFingerprint(FidoFingerprint(templateIdHexString, name))
|
fidoViewModel.addFingerprint(FidoFingerprint(templateIdHexString, name))
|
||||||
fidoViewModel.setSessionState(Session(fidoSession.info, pinStore.hasPin()))
|
fidoViewModel.setSessionState(Session(fidoSession.info, pinStore.hasPin(), pinRetries))
|
||||||
|
|
||||||
return@useSession JSONObject(
|
return@useSession JSONObject(
|
||||||
mapOf(
|
mapOf(
|
||||||
|
@ -221,7 +221,7 @@ class FidoResetHelper(
|
|||||||
private fun doReset(fidoSession: YubiKitFidoSession) {
|
private fun doReset(fidoSession: YubiKitFidoSession) {
|
||||||
logger.debug("Calling FIDO reset")
|
logger.debug("Calling FIDO reset")
|
||||||
fidoSession.reset(resetCommandState)
|
fidoSession.reset(resetCommandState)
|
||||||
fidoViewModel.setSessionState(Session(fidoSession.info, true))
|
fidoViewModel.setSessionState(Session(fidoSession.info, true, null))
|
||||||
fidoViewModel.updateCredentials(emptyList())
|
fidoViewModel.updateCredentials(emptyList())
|
||||||
pinStore.setPin(null)
|
pinStore.setPin(null)
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@ import com.yubico.authenticator.ViewModelData
|
|||||||
import com.yubico.authenticator.fido.data.FidoCredential
|
import com.yubico.authenticator.fido.data.FidoCredential
|
||||||
import com.yubico.authenticator.fido.data.FidoFingerprint
|
import com.yubico.authenticator.fido.data.FidoFingerprint
|
||||||
import com.yubico.authenticator.fido.data.Session
|
import com.yubico.authenticator.fido.data.Session
|
||||||
import org.json.JSONObject
|
|
||||||
|
|
||||||
class FidoViewModel : ViewModel() {
|
class FidoViewModel : ViewModel() {
|
||||||
private val _sessionState = MutableLiveData<ViewModelData>()
|
private val _sessionState = MutableLiveData<ViewModelData>()
|
||||||
@ -39,10 +38,6 @@ class FidoViewModel : ViewModel() {
|
|||||||
_sessionState.postValue(ViewModelData.Empty)
|
_sessionState.postValue(ViewModelData.Empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSessionLoadingState() {
|
|
||||||
_sessionState.postValue(ViewModelData.Loading)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val _credentials = MutableLiveData<List<FidoCredential>>()
|
private val _credentials = MutableLiveData<List<FidoCredential>>()
|
||||||
val credentials: LiveData<List<FidoCredential>> = _credentials
|
val credentials: LiveData<List<FidoCredential>> = _credentials
|
||||||
|
|
||||||
|
@ -86,12 +86,13 @@ data class SessionInfo(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Session(
|
data class Session(
|
||||||
@SerialName("info")
|
|
||||||
val info: SessionInfo,
|
val info: SessionInfo,
|
||||||
val unlocked: Boolean
|
val unlocked: Boolean,
|
||||||
|
@SerialName("pin_retries")
|
||||||
|
val pinRetries: Int?
|
||||||
) : JsonSerializable {
|
) : JsonSerializable {
|
||||||
constructor(infoData: InfoData, unlocked: Boolean) : this(
|
constructor(infoData: InfoData, unlocked: Boolean, pinRetries: Int?) : this(
|
||||||
SessionInfo(infoData), unlocked
|
SessionInfo(infoData), unlocked, pinRetries
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun toJson(): String {
|
override fun toJson(): String {
|
||||||
|
@ -88,7 +88,6 @@ class Ctap2Node(RpcNode):
|
|||||||
self.ctap = Ctap2(connection)
|
self.ctap = Ctap2(connection)
|
||||||
self._info = self.ctap.info
|
self._info = self.ctap.info
|
||||||
self.client_pin = ClientPin(self.ctap)
|
self.client_pin = ClientPin(self.ctap)
|
||||||
self._auth_blocked = False
|
|
||||||
self._token = None
|
self._token = None
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
@ -96,7 +95,6 @@ class Ctap2Node(RpcNode):
|
|||||||
logger.debug(f"Info: {self._info}")
|
logger.debug(f"Info: {self._info}")
|
||||||
data = dict(
|
data = dict(
|
||||||
info=asdict(self._info),
|
info=asdict(self._info),
|
||||||
auth_blocked=self._auth_blocked,
|
|
||||||
unlocked=self._token is not None,
|
unlocked=self._token is not None,
|
||||||
)
|
)
|
||||||
if self._info.options.get("clientPin"):
|
if self._info.options.get("clientPin"):
|
||||||
@ -190,7 +188,6 @@ class Ctap2Node(RpcNode):
|
|||||||
if e.code == CtapError.ERR.USER_ACTION_TIMEOUT:
|
if e.code == CtapError.ERR.USER_ACTION_TIMEOUT:
|
||||||
raise InactivityException()
|
raise InactivityException()
|
||||||
self._info = self.ctap.get_info()
|
self._info = self.ctap.get_info()
|
||||||
self._auth_blocked = False
|
|
||||||
self._token = None
|
self._token = None
|
||||||
return dict()
|
return dict()
|
||||||
|
|
||||||
|
@ -153,6 +153,7 @@ class _DesktopFidoStateNotifier extends FidoStateNotifier {
|
|||||||
return unlock(newPin);
|
return unlock(newPin);
|
||||||
} on RpcError catch (e) {
|
} on RpcError catch (e) {
|
||||||
if (e.status == 'pin-validation') {
|
if (e.status == 'pin-validation') {
|
||||||
|
ref.invalidateSelf();
|
||||||
return PinResult.failed(FidoPinFailureReason.invalidPin(
|
return PinResult.failed(FidoPinFailureReason.invalidPin(
|
||||||
e.body['retries'], e.body['auth_blocked']));
|
e.body['retries'], e.body['auth_blocked']));
|
||||||
}
|
}
|
||||||
@ -176,6 +177,7 @@ class _DesktopFidoStateNotifier extends FidoStateNotifier {
|
|||||||
} on RpcError catch (e) {
|
} on RpcError catch (e) {
|
||||||
if (e.status == 'pin-validation') {
|
if (e.status == 'pin-validation') {
|
||||||
_pinController.state = null;
|
_pinController.state = null;
|
||||||
|
ref.invalidateSelf();
|
||||||
return PinResult.failed(FidoPinFailureReason.invalidPin(
|
return PinResult.failed(FidoPinFailureReason.invalidPin(
|
||||||
e.body['retries'], e.body['auth_blocked']));
|
e.body['retries'], e.body['auth_blocked']));
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,8 @@ class FidoState with _$FidoState {
|
|||||||
|
|
||||||
factory FidoState(
|
factory FidoState(
|
||||||
{required Map<String, dynamic> info,
|
{required Map<String, dynamic> info,
|
||||||
required bool unlocked}) = _FidoState;
|
required bool unlocked,
|
||||||
|
int? pinRetries}) = _FidoState;
|
||||||
|
|
||||||
factory FidoState.fromJson(Map<String, dynamic> json) =>
|
factory FidoState.fromJson(Map<String, dynamic> json) =>
|
||||||
_$FidoStateFromJson(json);
|
_$FidoStateFromJson(json);
|
||||||
@ -47,6 +48,8 @@ class FidoState with _$FidoState {
|
|||||||
bool get alwaysUv => info['options']['alwaysUv'] == true;
|
bool get alwaysUv => info['options']['alwaysUv'] == true;
|
||||||
|
|
||||||
bool get forcePinChange => info['force_pin_change'] == true;
|
bool get forcePinChange => info['force_pin_change'] == true;
|
||||||
|
|
||||||
|
bool get pinBlocked => pinRetries == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
|
@ -22,6 +22,7 @@ FidoState _$FidoStateFromJson(Map<String, dynamic> json) {
|
|||||||
mixin _$FidoState {
|
mixin _$FidoState {
|
||||||
Map<String, dynamic> get info => throw _privateConstructorUsedError;
|
Map<String, dynamic> get info => throw _privateConstructorUsedError;
|
||||||
bool get unlocked => throw _privateConstructorUsedError;
|
bool get unlocked => throw _privateConstructorUsedError;
|
||||||
|
int? get pinRetries => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@ -34,7 +35,7 @@ abstract class $FidoStateCopyWith<$Res> {
|
|||||||
factory $FidoStateCopyWith(FidoState value, $Res Function(FidoState) then) =
|
factory $FidoStateCopyWith(FidoState value, $Res Function(FidoState) then) =
|
||||||
_$FidoStateCopyWithImpl<$Res, FidoState>;
|
_$FidoStateCopyWithImpl<$Res, FidoState>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({Map<String, dynamic> info, bool unlocked});
|
$Res call({Map<String, dynamic> info, bool unlocked, int? pinRetries});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -52,6 +53,7 @@ class _$FidoStateCopyWithImpl<$Res, $Val extends FidoState>
|
|||||||
$Res call({
|
$Res call({
|
||||||
Object? info = null,
|
Object? info = null,
|
||||||
Object? unlocked = null,
|
Object? unlocked = null,
|
||||||
|
Object? pinRetries = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
info: null == info
|
info: null == info
|
||||||
@ -62,6 +64,10 @@ class _$FidoStateCopyWithImpl<$Res, $Val extends FidoState>
|
|||||||
? _value.unlocked
|
? _value.unlocked
|
||||||
: unlocked // ignore: cast_nullable_to_non_nullable
|
: unlocked // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
pinRetries: freezed == pinRetries
|
||||||
|
? _value.pinRetries
|
||||||
|
: pinRetries // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,7 +80,7 @@ abstract class _$$FidoStateImplCopyWith<$Res>
|
|||||||
__$$FidoStateImplCopyWithImpl<$Res>;
|
__$$FidoStateImplCopyWithImpl<$Res>;
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({Map<String, dynamic> info, bool unlocked});
|
$Res call({Map<String, dynamic> info, bool unlocked, int? pinRetries});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -90,6 +96,7 @@ class __$$FidoStateImplCopyWithImpl<$Res>
|
|||||||
$Res call({
|
$Res call({
|
||||||
Object? info = null,
|
Object? info = null,
|
||||||
Object? unlocked = null,
|
Object? unlocked = null,
|
||||||
|
Object? pinRetries = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$FidoStateImpl(
|
return _then(_$FidoStateImpl(
|
||||||
info: null == info
|
info: null == info
|
||||||
@ -100,6 +107,10 @@ class __$$FidoStateImplCopyWithImpl<$Res>
|
|||||||
? _value.unlocked
|
? _value.unlocked
|
||||||
: unlocked // ignore: cast_nullable_to_non_nullable
|
: unlocked // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
pinRetries: freezed == pinRetries
|
||||||
|
? _value.pinRetries
|
||||||
|
: pinRetries // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,7 +119,9 @@ class __$$FidoStateImplCopyWithImpl<$Res>
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class _$FidoStateImpl extends _FidoState {
|
class _$FidoStateImpl extends _FidoState {
|
||||||
_$FidoStateImpl(
|
_$FidoStateImpl(
|
||||||
{required final Map<String, dynamic> info, required this.unlocked})
|
{required final Map<String, dynamic> info,
|
||||||
|
required this.unlocked,
|
||||||
|
this.pinRetries})
|
||||||
: _info = info,
|
: _info = info,
|
||||||
super._();
|
super._();
|
||||||
|
|
||||||
@ -125,10 +138,12 @@ class _$FidoStateImpl extends _FidoState {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
final bool unlocked;
|
final bool unlocked;
|
||||||
|
@override
|
||||||
|
final int? pinRetries;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'FidoState(info: $info, unlocked: $unlocked)';
|
return 'FidoState(info: $info, unlocked: $unlocked, pinRetries: $pinRetries)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -138,13 +153,15 @@ class _$FidoStateImpl extends _FidoState {
|
|||||||
other is _$FidoStateImpl &&
|
other is _$FidoStateImpl &&
|
||||||
const DeepCollectionEquality().equals(other._info, _info) &&
|
const DeepCollectionEquality().equals(other._info, _info) &&
|
||||||
(identical(other.unlocked, unlocked) ||
|
(identical(other.unlocked, unlocked) ||
|
||||||
other.unlocked == unlocked));
|
other.unlocked == unlocked) &&
|
||||||
|
(identical(other.pinRetries, pinRetries) ||
|
||||||
|
other.pinRetries == pinRetries));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(
|
int get hashCode => Object.hash(runtimeType,
|
||||||
runtimeType, const DeepCollectionEquality().hash(_info), unlocked);
|
const DeepCollectionEquality().hash(_info), unlocked, pinRetries);
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
@ -163,7 +180,8 @@ class _$FidoStateImpl extends _FidoState {
|
|||||||
abstract class _FidoState extends FidoState {
|
abstract class _FidoState extends FidoState {
|
||||||
factory _FidoState(
|
factory _FidoState(
|
||||||
{required final Map<String, dynamic> info,
|
{required final Map<String, dynamic> info,
|
||||||
required final bool unlocked}) = _$FidoStateImpl;
|
required final bool unlocked,
|
||||||
|
final int? pinRetries}) = _$FidoStateImpl;
|
||||||
_FidoState._() : super._();
|
_FidoState._() : super._();
|
||||||
|
|
||||||
factory _FidoState.fromJson(Map<String, dynamic> json) =
|
factory _FidoState.fromJson(Map<String, dynamic> json) =
|
||||||
@ -174,6 +192,8 @@ abstract class _FidoState extends FidoState {
|
|||||||
@override
|
@override
|
||||||
bool get unlocked;
|
bool get unlocked;
|
||||||
@override
|
@override
|
||||||
|
int? get pinRetries;
|
||||||
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$FidoStateImplCopyWith<_$FidoStateImpl> get copyWith =>
|
_$$FidoStateImplCopyWith<_$FidoStateImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
|
@ -10,12 +10,14 @@ _$FidoStateImpl _$$FidoStateImplFromJson(Map<String, dynamic> json) =>
|
|||||||
_$FidoStateImpl(
|
_$FidoStateImpl(
|
||||||
info: json['info'] as Map<String, dynamic>,
|
info: json['info'] as Map<String, dynamic>,
|
||||||
unlocked: json['unlocked'] as bool,
|
unlocked: json['unlocked'] as bool,
|
||||||
|
pinRetries: json['pin_retries'] as int?,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$FidoStateImplToJson(_$FidoStateImpl instance) =>
|
Map<String, dynamic> _$$FidoStateImplToJson(_$FidoStateImpl instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'info': instance.info,
|
'info': instance.info,
|
||||||
'unlocked': instance.unlocked,
|
'unlocked': instance.unlocked,
|
||||||
|
'pin_retries': instance.pinRetries,
|
||||||
};
|
};
|
||||||
|
|
||||||
_$FingerprintImpl _$$FingerprintImplFromJson(Map<String, dynamic> json) =>
|
_$FingerprintImpl _$$FingerprintImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
@ -48,6 +48,7 @@ Widget _fidoBuildActions(BuildContext context, DeviceNode node, FidoState state,
|
|||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
final colors = Theme.of(context).buttonTheme.colorScheme ??
|
final colors = Theme.of(context).buttonTheme.colorScheme ??
|
||||||
Theme.of(context).colorScheme;
|
Theme.of(context).colorScheme;
|
||||||
|
final authBlocked = state.pinBlocked;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
@ -90,21 +91,30 @@ Widget _fidoBuildActions(BuildContext context, DeviceNode node, FidoState state,
|
|||||||
feature: features.actionsPin,
|
feature: features.actionsPin,
|
||||||
icon: const Icon(Symbols.pin),
|
icon: const Icon(Symbols.pin),
|
||||||
title: state.hasPin ? l10n.s_change_pin : l10n.s_set_pin,
|
title: state.hasPin ? l10n.s_change_pin : l10n.s_set_pin,
|
||||||
subtitle: state.hasPin
|
subtitle: authBlocked
|
||||||
|
? l10n.l_pin_blocked
|
||||||
|
: state.hasPin
|
||||||
? (state.forcePinChange
|
? (state.forcePinChange
|
||||||
? l10n.s_pin_change_required
|
? l10n.s_pin_change_required
|
||||||
|
: state.pinRetries != null
|
||||||
|
? l10n.l_attempts_remaining(state.pinRetries!)
|
||||||
: l10n.s_fido_pin_protection)
|
: l10n.s_fido_pin_protection)
|
||||||
: l10n.s_fido_pin_protection,
|
: l10n.s_fido_pin_protection,
|
||||||
trailing: state.alwaysUv && !state.hasPin || state.forcePinChange
|
trailing: authBlocked ||
|
||||||
|
state.alwaysUv && !state.hasPin ||
|
||||||
|
state.forcePinChange
|
||||||
? Icon(Symbols.warning_amber, color: colors.tertiary)
|
? Icon(Symbols.warning_amber, color: colors.tertiary)
|
||||||
: null,
|
: null,
|
||||||
onTap: (context) {
|
onTap: !authBlocked
|
||||||
|
? (context) {
|
||||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||||
showBlurDialog(
|
showBlurDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => FidoPinDialog(node.path, state),
|
builder: (context) => FidoPinDialog(node.path, state),
|
||||||
);
|
);
|
||||||
}),
|
}
|
||||||
|
: null,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -87,6 +87,8 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
|
|||||||
final hasPinComplexity =
|
final hasPinComplexity =
|
||||||
ref.read(currentDeviceDataProvider).valueOrNull?.info.pinComplexity ??
|
ref.read(currentDeviceDataProvider).valueOrNull?.info.pinComplexity ??
|
||||||
false;
|
false;
|
||||||
|
final pinRetries = ref.watch(fidoStateProvider(widget.devicePath)
|
||||||
|
.select((s) => s.whenOrNull(data: (state) => state.pinRetries)));
|
||||||
|
|
||||||
return ResponsiveDialog(
|
return ResponsiveDialog(
|
||||||
title: Text(hasPin ? l10n.s_change_pin : l10n.s_set_pin),
|
title: Text(hasPin ? l10n.s_change_pin : l10n.s_set_pin),
|
||||||
@ -115,6 +117,9 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
|
|||||||
enabled: !_isBlocked,
|
enabled: !_isBlocked,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
labelText: l10n.s_current_pin,
|
labelText: l10n.s_current_pin,
|
||||||
|
helperText: pinRetries != null && pinRetries <= 3
|
||||||
|
? l10n.l_attempts_remaining(pinRetries)
|
||||||
|
: '', // Prevents dialog resizing
|
||||||
errorText: _currentIsWrong ? _currentPinError : null,
|
errorText: _currentIsWrong ? _currentPinError : null,
|
||||||
errorMaxLines: 3,
|
errorMaxLines: 3,
|
||||||
prefixIcon: const Icon(Symbols.pin),
|
prefixIcon: const Icon(Symbols.pin),
|
||||||
@ -249,8 +254,10 @@ class _FidoPinDialogState extends ConsumerState<FidoPinDialog> {
|
|||||||
extentOffset: _currentPinController.text.length);
|
extentOffset: _currentPinController.text.length);
|
||||||
_currentPinFocus.requestFocus();
|
_currentPinFocus.requestFocus();
|
||||||
setState(() {
|
setState(() {
|
||||||
if (authBlocked) {
|
if (authBlocked || retries == 0) {
|
||||||
_currentPinError = l10n.l_pin_soft_locked;
|
_currentPinError = retries == 0
|
||||||
|
? l10n.l_pin_blocked_reset
|
||||||
|
: l10n.l_pin_soft_locked;
|
||||||
_currentIsWrong = true;
|
_currentIsWrong = true;
|
||||||
_isBlocked = true;
|
_isBlocked = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -82,7 +82,7 @@ class _PinEntryFormState extends ConsumerState<PinEntryForm> {
|
|||||||
|
|
||||||
String? _getErrorText() {
|
String? _getErrorText() {
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
if (_retries == 0) {
|
if (widget._state.pinBlocked || _retries == 0) {
|
||||||
return l10n.l_pin_blocked_reset;
|
return l10n.l_pin_blocked_reset;
|
||||||
}
|
}
|
||||||
if (_blocked) {
|
if (_blocked) {
|
||||||
@ -98,6 +98,8 @@ class _PinEntryFormState extends ConsumerState<PinEntryForm> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
final noFingerprints = widget._state.bioEnroll == false;
|
final noFingerprints = widget._state.bioEnroll == false;
|
||||||
|
final authBlocked = widget._state.pinBlocked;
|
||||||
|
final pinRetries = widget._state.pinRetries;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 18.0, right: 18, top: 8),
|
padding: const EdgeInsets.only(left: 18.0, right: 18, top: 8),
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -113,12 +115,14 @@ class _PinEntryFormState extends ConsumerState<PinEntryForm> {
|
|||||||
autofillHints: const [AutofillHints.password],
|
autofillHints: const [AutofillHints.password],
|
||||||
controller: _pinController,
|
controller: _pinController,
|
||||||
focusNode: _pinFocus,
|
focusNode: _pinFocus,
|
||||||
enabled: !_blocked && (_retries ?? 1) > 0,
|
enabled: !authBlocked && !_blocked && (_retries ?? 1) > 0,
|
||||||
decoration: AppInputDecoration(
|
decoration: AppInputDecoration(
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
labelText: l10n.s_pin,
|
labelText: l10n.s_pin,
|
||||||
helperText: '', // Prevents dialog resizing
|
helperText: pinRetries != null && pinRetries <= 3
|
||||||
errorText: _pinIsWrong ? _getErrorText() : null,
|
? l10n.l_attempts_remaining(pinRetries)
|
||||||
|
: '', // Prevents dialog resizing
|
||||||
|
errorText: _pinIsWrong || authBlocked ? _getErrorText() : null,
|
||||||
errorMaxLines: 3,
|
errorMaxLines: 3,
|
||||||
prefixIcon: const Icon(Symbols.pin),
|
prefixIcon: const Icon(Symbols.pin),
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
|
@ -295,6 +295,7 @@
|
|||||||
"l_fido_pin_protection_optional": "Optionaler FIDO PIN Schutz",
|
"l_fido_pin_protection_optional": "Optionaler FIDO PIN Schutz",
|
||||||
"l_enter_fido2_pin": "Geben Sie die FIDO2 PIN für Ihren YubiKey ein",
|
"l_enter_fido2_pin": "Geben Sie die FIDO2 PIN für Ihren YubiKey ein",
|
||||||
"l_pin_blocked_reset": "PIN ist blockiert; setzen Sie die FIDO Anwendung auf Werkseinstellung zurück",
|
"l_pin_blocked_reset": "PIN ist blockiert; setzen Sie die FIDO Anwendung auf Werkseinstellung zurück",
|
||||||
|
"l_pin_blocked": null,
|
||||||
"l_set_pin_first": "Zuerst ist eine PIN erforderlich",
|
"l_set_pin_first": "Zuerst ist eine PIN erforderlich",
|
||||||
"l_unlock_pin_first": "Zuerst mit PIN entsperren",
|
"l_unlock_pin_first": "Zuerst mit PIN entsperren",
|
||||||
"l_pin_soft_locked": "PIN wurde blockiert bis der YubiKey entfernt und wieder angeschlossen wird",
|
"l_pin_soft_locked": "PIN wurde blockiert bis der YubiKey entfernt und wieder angeschlossen wird",
|
||||||
|
@ -295,6 +295,7 @@
|
|||||||
"l_fido_pin_protection_optional": "Optional FIDO PIN protection",
|
"l_fido_pin_protection_optional": "Optional FIDO PIN protection",
|
||||||
"l_enter_fido2_pin": "Enter the FIDO2 PIN for your YubiKey",
|
"l_enter_fido2_pin": "Enter the FIDO2 PIN for your YubiKey",
|
||||||
"l_pin_blocked_reset": "PIN is blocked; factory reset the FIDO application",
|
"l_pin_blocked_reset": "PIN is blocked; factory reset the FIDO application",
|
||||||
|
"l_pin_blocked": "PIN is blocked",
|
||||||
"l_set_pin_first": "A PIN is required first",
|
"l_set_pin_first": "A PIN is required first",
|
||||||
"l_unlock_pin_first": "Unlock with PIN first",
|
"l_unlock_pin_first": "Unlock with PIN first",
|
||||||
"l_pin_soft_locked": "PIN has been blocked until the YubiKey is removed and reinserted",
|
"l_pin_soft_locked": "PIN has been blocked until the YubiKey is removed and reinserted",
|
||||||
|
@ -295,6 +295,7 @@
|
|||||||
"l_fido_pin_protection_optional": "PIN de protection optionnel FIDO",
|
"l_fido_pin_protection_optional": "PIN de protection optionnel FIDO",
|
||||||
"l_enter_fido2_pin": "Entrez le PIN FIDO2 de votre YubiKey",
|
"l_enter_fido2_pin": "Entrez le PIN FIDO2 de votre YubiKey",
|
||||||
"l_pin_blocked_reset": "PIN bloqué; Réinitialisez à l'état d'usine le FIDO",
|
"l_pin_blocked_reset": "PIN bloqué; Réinitialisez à l'état d'usine le FIDO",
|
||||||
|
"l_pin_blocked": null,
|
||||||
"l_set_pin_first": "Un PIN est d'abord requis",
|
"l_set_pin_first": "Un PIN est d'abord requis",
|
||||||
"l_unlock_pin_first": "Débloquez avec un PIN d'abord",
|
"l_unlock_pin_first": "Débloquez avec un PIN d'abord",
|
||||||
"l_pin_soft_locked": "Le PIN est bloqué tant que votre YubiKey ne sera pas réinsérée",
|
"l_pin_soft_locked": "Le PIN est bloqué tant que votre YubiKey ne sera pas réinsérée",
|
||||||
|
@ -295,6 +295,7 @@
|
|||||||
"l_fido_pin_protection_optional": "任意FIDO PINによる保護",
|
"l_fido_pin_protection_optional": "任意FIDO PINによる保護",
|
||||||
"l_enter_fido2_pin": "YubiKeyのFIDO2 PINを入力してください",
|
"l_enter_fido2_pin": "YubiKeyのFIDO2 PINを入力してください",
|
||||||
"l_pin_blocked_reset": "PINはブロックされています。FIDOアプリケーションを出荷時設定にリセットしてください",
|
"l_pin_blocked_reset": "PINはブロックされています。FIDOアプリケーションを出荷時設定にリセットしてください",
|
||||||
|
"l_pin_blocked": null,
|
||||||
"l_set_pin_first": "最初にPINが必要です",
|
"l_set_pin_first": "最初にPINが必要です",
|
||||||
"l_unlock_pin_first": "最初にPINでロックを解除してください",
|
"l_unlock_pin_first": "最初にPINでロックを解除してください",
|
||||||
"l_pin_soft_locked": "YubiKeyを取り外して再挿入するまで、PINはブロックされています",
|
"l_pin_soft_locked": "YubiKeyを取り外して再挿入するまで、PINはブロックされています",
|
||||||
|
@ -295,6 +295,7 @@
|
|||||||
"l_fido_pin_protection_optional": "Opcjonalna ochrona FIDO kodem PIN",
|
"l_fido_pin_protection_optional": "Opcjonalna ochrona FIDO kodem PIN",
|
||||||
"l_enter_fido2_pin": "Wprowadź kod PIN FIDO2 klucza YubiKey",
|
"l_enter_fido2_pin": "Wprowadź kod PIN FIDO2 klucza YubiKey",
|
||||||
"l_pin_blocked_reset": "PIN jest zablokowany; przywróć ustawienia fabryczne funkcji FIDO",
|
"l_pin_blocked_reset": "PIN jest zablokowany; przywróć ustawienia fabryczne funkcji FIDO",
|
||||||
|
"l_pin_blocked": null,
|
||||||
"l_set_pin_first": "Najpierw wymagany jest kod PIN",
|
"l_set_pin_first": "Najpierw wymagany jest kod PIN",
|
||||||
"l_unlock_pin_first": "Najpierw odblokuj kodem PIN",
|
"l_unlock_pin_first": "Najpierw odblokuj kodem PIN",
|
||||||
"l_pin_soft_locked": "PIN został zablokowany do momentu ponownego podłączenia klucza YubiKey",
|
"l_pin_soft_locked": "PIN został zablokowany do momentu ponownego podłączenia klucza YubiKey",
|
||||||
|
Loading…
Reference in New Issue
Block a user