mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-23 10:11:52 +03:00
serialize model credentials as List
This commit is contained in:
parent
b7ee31c70c
commit
0a65ad6a73
@ -3,8 +3,6 @@ package com.yubico.authenticator.oath
|
|||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
val jsonSerializer = Json {
|
val jsonSerializer = Json {
|
||||||
// allows us to use Credential as a key
|
|
||||||
allowStructuredMapKeys = true
|
|
||||||
// creates properties for default values
|
// creates properties for default values
|
||||||
encodeDefaults = true
|
encodeDefaults = true
|
||||||
}
|
}
|
@ -93,36 +93,41 @@ class Model {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var _credentials = mutableMapOf<Credential, Code?>(); private set
|
||||||
|
|
||||||
var session = Session()
|
var session = Session()
|
||||||
var credentials = mutableMapOf<Credential, Code?>(); private set
|
val credentials: List<CredentialWithCode>
|
||||||
|
get() = _credentials.map {
|
||||||
|
CredentialWithCode(it.key, it.value)
|
||||||
|
}
|
||||||
|
|
||||||
// resets the model to initial values
|
// resets the model to initial values
|
||||||
// used when a usb key has been disconnected
|
// used when a usb key has been disconnected
|
||||||
fun reset() {
|
fun reset() {
|
||||||
this.credentials.clear()
|
this._credentials.clear()
|
||||||
this.session = Session()
|
this.session = Session()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(deviceId: String, credentials: Map<Credential, Code?>) {
|
fun update(deviceId: String, credentials: Map<Credential, Code?>) {
|
||||||
if (this.session.deviceId != deviceId) {
|
if (this.session.deviceId != deviceId) {
|
||||||
// device was changed, we use the new list
|
// device was changed, we use the new list
|
||||||
this.credentials.clear()
|
this._credentials.clear()
|
||||||
this.credentials.putAll(from = credentials)
|
this._credentials.putAll(from = credentials)
|
||||||
this.session = Session(deviceId)
|
this.session = Session(deviceId)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// update codes for non interactive keys
|
// update codes for non interactive keys
|
||||||
for ((credential, code) in credentials) {
|
for ((credential, code) in credentials) {
|
||||||
if (!credential.isInteractive()) {
|
if (!credential.isInteractive()) {
|
||||||
this.credentials[credential] = code
|
this._credentials[credential] = code
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// remove obsolete credentials
|
// remove obsolete credentials
|
||||||
this.credentials.filter { entry ->
|
this._credentials.filter { entry ->
|
||||||
// get only keys which are not present in the input map
|
// get only keys which are not present in the input map
|
||||||
!credentials.contains(entry.key)
|
!credentials.contains(entry.key)
|
||||||
}.forEach(action = {
|
}.forEach(action = {
|
||||||
this.credentials.remove(it.key)
|
this._credentials.remove(it.key)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,7 +137,7 @@ class Model {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
credentials[credential] = code
|
_credentials[credential] = code
|
||||||
|
|
||||||
return CredentialWithCode(credential, code)
|
return CredentialWithCode(credential, code)
|
||||||
}
|
}
|
||||||
@ -150,15 +155,15 @@ class Model {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!credentials.contains(oldCredential)) {
|
if (!_credentials.contains(oldCredential)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// preserve code
|
// preserve code
|
||||||
val code = credentials[oldCredential]
|
val code = _credentials[oldCredential]
|
||||||
|
|
||||||
credentials.remove(oldCredential)
|
_credentials.remove(oldCredential)
|
||||||
credentials[newCredential] = code
|
_credentials[newCredential] = code
|
||||||
|
|
||||||
return newCredential
|
return newCredential
|
||||||
}
|
}
|
||||||
@ -168,11 +173,11 @@ class Model {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!credentials.contains(credential)) {
|
if (!_credentials.contains(credential)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
credentials[credential] = code
|
_credentials[credential] = code
|
||||||
|
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
@ -105,9 +105,9 @@ class ModelTest {
|
|||||||
|
|
||||||
assertEquals("device1", model.session.deviceId)
|
assertEquals("device1", model.session.deviceId)
|
||||||
assertEquals(3, model.credentials.size)
|
assertEquals(3, model.credentials.size)
|
||||||
assertTrue(model.credentials.containsKey(cred1))
|
assertTrue(model.credentials.find { it.credential == cred1 } != null)
|
||||||
assertTrue(model.credentials.containsKey(cred2))
|
assertTrue(model.credentials.find { it.credential == cred2 } != null)
|
||||||
assertTrue(model.credentials.containsKey(cred3))
|
assertTrue(model.credentials.find { it.credential == cred3 } != null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -117,13 +117,13 @@ class ModelTest {
|
|||||||
val m1 = mapOf(cred to code)
|
val m1 = mapOf(cred to code)
|
||||||
model.update(cred.deviceId, m1)
|
model.update(cred.deviceId, m1)
|
||||||
|
|
||||||
assertTrue(model.credentials.containsValue(code))
|
assertTrue(model.credentials.find { it.code == code } != null)
|
||||||
|
|
||||||
val updatedCode = code(value = "121212")
|
val updatedCode = code(value = "121212")
|
||||||
val m2 = mapOf(cred to updatedCode)
|
val m2 = mapOf(cred to updatedCode)
|
||||||
model.update(cred.deviceId, m2)
|
model.update(cred.deviceId, m2)
|
||||||
|
|
||||||
assertTrue(model.credentials.containsValue(updatedCode))
|
assertTrue(model.credentials.find { it.code == updatedCode } != null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -138,16 +138,16 @@ class ModelTest {
|
|||||||
val m1 = mapOf(hotp to hotpCode, totp to totpCode)
|
val m1 = mapOf(hotp to hotpCode, totp to totpCode)
|
||||||
model.update(d, m1)
|
model.update(d, m1)
|
||||||
|
|
||||||
assertTrue(model.credentials.containsValue(hotpCode))
|
assertTrue(model.credentials.find { it.code == hotpCode } != null)
|
||||||
|
|
||||||
val updatedTotpCode = code(value = "121212")
|
val updatedTotpCode = code(value = "121212")
|
||||||
val updatedHotpCode = code(value = "098765")
|
val updatedHotpCode = code(value = "098765")
|
||||||
val m2 = mapOf(hotp to updatedHotpCode, totp to updatedTotpCode)
|
val m2 = mapOf(hotp to updatedHotpCode, totp to updatedTotpCode)
|
||||||
model.update(d, m2)
|
model.update(d, m2)
|
||||||
|
|
||||||
assertTrue(model.credentials.containsValue(updatedTotpCode))
|
assertTrue(model.credentials.find { it.code == updatedTotpCode } != null)
|
||||||
assertTrue(model.credentials.containsValue(hotpCode))
|
assertTrue(model.credentials.find { it.code == hotpCode } != null)
|
||||||
assertFalse(model.credentials.containsValue(updatedHotpCode))
|
assertFalse(model.credentials.find { it.code == updatedHotpCode } != null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -166,7 +166,7 @@ class ModelTest {
|
|||||||
model.update(d, mapOf(totp to newCode))
|
model.update(d, mapOf(totp to newCode))
|
||||||
|
|
||||||
assertEquals(1, model.credentials.size)
|
assertEquals(1, model.credentials.size)
|
||||||
assertEquals("00000", model.credentials[totp]?.value)
|
assertEquals("00000", model.credentials.find { it.credential == totp }?.code?.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -203,7 +203,7 @@ class ModelTest {
|
|||||||
|
|
||||||
// only t1 is part of credentials
|
// only t1 is part of credentials
|
||||||
assertEquals(1, model.credentials.size)
|
assertEquals(1, model.credentials.size)
|
||||||
assertTrue(model.credentials.containsKey(t1))
|
assertTrue(model.credentials.find { it.credential == t1 } != null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -7,8 +7,7 @@ import kotlinx.serialization.json.JsonArray
|
|||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
import kotlinx.serialization.json.encodeToJsonElement
|
import kotlinx.serialization.json.encodeToJsonElement
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.*
|
||||||
import org.junit.Assert.assertTrue
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class SerializationTest {
|
class SerializationTest {
|
||||||
@ -16,7 +15,7 @@ class SerializationTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `serialization settings`() {
|
fun `serialization settings`() {
|
||||||
assertTrue(jsonSerializer.configuration.encodeDefaults)
|
assertTrue(jsonSerializer.configuration.encodeDefaults)
|
||||||
assertTrue(jsonSerializer.configuration.allowStructuredMapKeys)
|
assertFalse(jsonSerializer.configuration.allowStructuredMapKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -94,32 +93,25 @@ class SerializationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `credentials json type`() {
|
fun `credentials json type`() {
|
||||||
val m = mapOf(totp() to code(), hotp() to code())
|
val l = listOf(
|
||||||
|
Model.CredentialWithCode(totp(), code()), Model.CredentialWithCode(hotp(), code()),
|
||||||
|
)
|
||||||
|
|
||||||
val jsonElement = jsonSerializer.encodeToJsonElement(m)
|
val jsonElement = jsonSerializer.encodeToJsonElement(l)
|
||||||
assertTrue(jsonElement is JsonArray)
|
assertTrue(jsonElement is JsonArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `credentials json size`() {
|
fun `credentials json size`() {
|
||||||
val m1 = mapOf<Model.Credential, Model.Code?>()
|
val l1 = listOf<Model.CredentialWithCode>()
|
||||||
val jsonElement1 = jsonSerializer.encodeToJsonElement(m1) as JsonArray
|
val jsonElement1 = jsonSerializer.encodeToJsonElement(l1) as JsonArray
|
||||||
assertEquals(0, jsonElement1.size)
|
assertEquals(0, jsonElement1.size)
|
||||||
|
|
||||||
val m2 = mapOf(totp() to code(), hotp() to code())
|
val l2 = listOf(
|
||||||
val jsonElement2 = jsonSerializer.encodeToJsonElement(m2) as JsonArray
|
Model.CredentialWithCode(totp(), code()), Model.CredentialWithCode(hotp(), code()),
|
||||||
assertEquals(4, jsonElement2.size)
|
)
|
||||||
|
val jsonElement2 = jsonSerializer.encodeToJsonElement(l2) as JsonArray
|
||||||
|
assertEquals(2, jsonElement2.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `credentials json content`() {
|
|
||||||
val m = mapOf(totp() to code())
|
|
||||||
val jsonElement = jsonSerializer.encodeToJsonElement(m) as JsonArray
|
|
||||||
|
|
||||||
// the first element is Credential which has device_id property
|
|
||||||
assertTrue((jsonElement[0] as JsonObject).containsKey("device_id"))
|
|
||||||
|
|
||||||
// the second element is Credential which has value property
|
|
||||||
assertTrue((jsonElement[1] as JsonObject).containsKey("value"))
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -29,23 +29,10 @@ class _CredentialsProvider extends StateNotifier<List<OathPair>?> {
|
|||||||
void setFromString(String input) {
|
void setFromString(String input) {
|
||||||
var result = jsonDecode(input);
|
var result = jsonDecode(input);
|
||||||
|
|
||||||
/// structure of data in the json object is:
|
if (result is List) {
|
||||||
/// [credential1, code1, credential2, code2, ...]
|
state = result.map((e) => OathPair.fromJson(e)).toList();
|
||||||
|
} else {
|
||||||
final List<OathPair> pairs = [];
|
state = [];
|
||||||
if (result is List<dynamic>) {
|
|
||||||
for (var index = 0; index < result.length / 2; index++) {
|
|
||||||
final credential = result[index * 2];
|
|
||||||
final code = result[index * 2 + 1];
|
|
||||||
pairs.add(
|
|
||||||
OathPair(
|
|
||||||
OathCredential.fromJson(credential),
|
|
||||||
code == null ? null : OathCode.fromJson(code),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state = pairs;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,9 @@ class OathCode with _$OathCode {
|
|||||||
@freezed
|
@freezed
|
||||||
class OathPair with _$OathPair {
|
class OathPair with _$OathPair {
|
||||||
factory OathPair(OathCredential credential, OathCode? code) = _OathPair;
|
factory OathPair(OathCredential credential, OathCode? code) = _OathPair;
|
||||||
|
|
||||||
|
factory OathPair.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$OathPairFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
|
@ -474,6 +474,10 @@ abstract class _OathCode implements OathCode {
|
|||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OathPair _$OathPairFromJson(Map<String, dynamic> json) {
|
||||||
|
return _OathPair.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
class _$OathPairTearOff {
|
class _$OathPairTearOff {
|
||||||
const _$OathPairTearOff();
|
const _$OathPairTearOff();
|
||||||
@ -484,6 +488,10 @@ class _$OathPairTearOff {
|
|||||||
code,
|
code,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OathPair fromJson(Map<String, Object?> json) {
|
||||||
|
return OathPair.fromJson(json);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -494,6 +502,7 @@ mixin _$OathPair {
|
|||||||
OathCredential get credential => throw _privateConstructorUsedError;
|
OathCredential get credential => throw _privateConstructorUsedError;
|
||||||
OathCode? get code => throw _privateConstructorUsedError;
|
OathCode? get code => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
$OathPairCopyWith<OathPair> get copyWith =>
|
$OathPairCopyWith<OathPair> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
@ -594,10 +603,13 @@ class __$OathPairCopyWithImpl<$Res> extends _$OathPairCopyWithImpl<$Res>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
class _$_OathPair implements _OathPair {
|
class _$_OathPair implements _OathPair {
|
||||||
_$_OathPair(this.credential, this.code);
|
_$_OathPair(this.credential, this.code);
|
||||||
|
|
||||||
|
factory _$_OathPair.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$_OathPairFromJson(json);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final OathCredential credential;
|
final OathCredential credential;
|
||||||
@override
|
@override
|
||||||
@ -628,11 +640,18 @@ class _$_OathPair implements _OathPair {
|
|||||||
@override
|
@override
|
||||||
_$OathPairCopyWith<_OathPair> get copyWith =>
|
_$OathPairCopyWith<_OathPair> get copyWith =>
|
||||||
__$OathPairCopyWithImpl<_OathPair>(this, _$identity);
|
__$OathPairCopyWithImpl<_OathPair>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$_OathPairToJson(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _OathPair implements OathPair {
|
abstract class _OathPair implements OathPair {
|
||||||
factory _OathPair(OathCredential credential, OathCode? code) = _$_OathPair;
|
factory _OathPair(OathCredential credential, OathCode? code) = _$_OathPair;
|
||||||
|
|
||||||
|
factory _OathPair.fromJson(Map<String, dynamic> json) = _$_OathPair.fromJson;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
OathCredential get credential;
|
OathCredential get credential;
|
||||||
@override
|
@override
|
||||||
|
@ -46,6 +46,19 @@ Map<String, dynamic> _$$_OathCodeToJson(_$_OathCode instance) =>
|
|||||||
'valid_to': instance.validTo,
|
'valid_to': instance.validTo,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_$_OathPair _$$_OathPairFromJson(Map<String, dynamic> json) => _$_OathPair(
|
||||||
|
OathCredential.fromJson(json['credential'] as Map<String, dynamic>),
|
||||||
|
json['code'] == null
|
||||||
|
? null
|
||||||
|
: OathCode.fromJson(json['code'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$_OathPairToJson(_$_OathPair instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'credential': instance.credential,
|
||||||
|
'code': instance.code,
|
||||||
|
};
|
||||||
|
|
||||||
_$_OathState _$$_OathStateFromJson(Map<String, dynamic> json) => _$_OathState(
|
_$_OathState _$$_OathStateFromJson(Map<String, dynamic> json) => _$_OathState(
|
||||||
json['device_id'] as String,
|
json['device_id'] as String,
|
||||||
hasKey: json['has_key'] as bool,
|
hasKey: json['has_key'] as bool,
|
||||||
|
Loading…
Reference in New Issue
Block a user