Skip to content

Commit

Permalink
[SYNC-CONTACT-V2] Unit tests for FederationIdentityLookUpInteractor
Browse files Browse the repository at this point in the history
  • Loading branch information
hoangdat committed Mar 3, 2025
1 parent 2861d85 commit 09d572a
Show file tree
Hide file tree
Showing 9 changed files with 482 additions and 220 deletions.
10 changes: 4 additions & 6 deletions lib/domain/usecase/contacts/federation_look_up_argument.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import 'package:equatable/equatable.dart';
import 'package:fluffychat/domain/usecase/contacts/twake_look_up_argument.dart';

class FederationLookUpArgument with EquatableMixin {
final String homeServerUrl;
class FederationLookUpArgument extends TwakeLookUpArgument {
final String federationUrl;
final String withMxId;
final String withAccessToken;

FederationLookUpArgument({
required this.homeServerUrl,
required super.homeServerUrl,
required this.federationUrl,
required this.withMxId,
required this.withAccessToken,
required super.withAccessToken,
});

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import 'package:matrix/matrix.dart';
class FederationLookUpPhonebookContactInteractor {
final PhonebookContactRepository _phonebookContactRepository =
getIt.get<PhonebookContactRepository>();
final IdentityLookupManager _identityLookupManager =
getIt.get<IdentityLookupManager>();

Stream<Either<Failure, Success>> execute({
int lookupChunkSize = 10,
Expand All @@ -40,9 +42,6 @@ class FederationLookUpPhonebookContactInteractor {

FederationRegisterResponse? federationRegisterToken;

final IdentityLookupManager identityLookupManager =
IdentityLookupManager();

final FederationIdentityRequestTokenManager
federationIdentityRequestTokenManager =
FederationIdentityRequestTokenManager();
Expand Down Expand Up @@ -78,7 +77,7 @@ class FederationLookUpPhonebookContactInteractor {
}

try {
final res = await identityLookupManager.register(
final res = await _identityLookupManager.register(
federationUrl: argument.federationUrl,
tokenInformation: federationIdentityRequestTokenRes!,
);
Expand Down Expand Up @@ -124,7 +123,7 @@ class FederationLookUpPhonebookContactInteractor {
FederationHashDetailsResponse? hashDetails;

try {
final res = await identityLookupManager.getHashDetails(
final res = await _identityLookupManager.getHashDetails(
federationUrl: argument.federationUrl,
registeredToken: federationRegisterToken.token!,
);
Expand Down Expand Up @@ -214,7 +213,7 @@ class FederationLookUpPhonebookContactInteractor {
);
FederationLookupMxidResponse? response;
try {
response = await identityLookupManager.lookupMxid(
response = await _identityLookupManager.lookupMxid(
federationUrl: argument.federationUrl,
request: request,
registeredToken: federationRegisterToken.token!,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class LookUpFederationIdentityNotFoundException implements Exception {
final dynamic error;

LookUpFederationIdentityNotFoundException(this.error);

@override
String toString() {
return 'LookUpFederationIdentityNotFoundException $error';
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,12 @@
import 'dart:convert';

import 'package:crypto/crypto.dart';
import 'package:equatable/equatable.dart';
import 'package:fluffychat/modules/federation_identity_lookup/domain/models/federation_hash_details_response.dart';
import 'package:fluffychat/domain/model/contact/contact.dart';
import 'package:fluffychat/utils/string_extension.dart';
import 'package:matrix/encryption/utils/base64_unpadded.dart';

abstract class FederationThirdPartyContact with EquatableMixin {
final String? matrixId;

final String thirdPartyIdType;

final String thirdPartyId;

/// Support for multiple peppers
final Map<String, String>? thirdPartyIdToHashMap;

abstract class FederationThirdPartyContact extends ThirdPartyContact {
FederationThirdPartyContact({
this.matrixId,
required this.thirdPartyIdType,
required this.thirdPartyId,
this.thirdPartyIdToHashMap,
super.matrixId,
required super.thirdPartyIdType,
required super.thirdPartyId,
super.thirdPartyIdToHashMap,
});

@override
Expand All @@ -30,51 +16,6 @@ abstract class FederationThirdPartyContact with EquatableMixin {
thirdPartyId,
thirdPartyIdToHashMap,
];

String calculateHashWithAlgorithmSha256({
required String pepper,
}) {
final input = [thirdPartyId, thirdPartyIdType, pepper].join(' ');
final bytes = utf8.encode(input);
final lookupHash =
encodeBase64Unpadded(sha256.convert(bytes).bytes).urlSafeBase64;
return lookupHash;
}

String calculateHashWithoutAlgorithm() {
return [thirdPartyId, thirdPartyIdType].join(' ');
}

Set<String> calculateHashUsingAllPeppers({
required FederationHashDetailsResponse hashDetails,
}) {
final Set<String> hashes = {};

if (hashDetails.algorithms == null || hashDetails.algorithms!.isEmpty) {
final hash = calculateHashWithoutAlgorithm();
hashes.add(hash);
return hashes;
}

for (final algorithm in hashDetails.algorithms!) {
final peppers = {
hashDetails.lookupPepper,
};

for (final pepper in peppers) {
if (algorithm == 'sha256') {
final hash = calculateHashWithAlgorithmSha256(
pepper: pepper ?? '',
);
hashes.add(hash);
} else {
final hash = calculateHashWithoutAlgorithm();
hashes.add(hash);
}
}
}
return hashes;
}
}

class FederationPhone extends FederationThirdPartyContact {
Expand All @@ -85,7 +26,7 @@ class FederationPhone extends FederationThirdPartyContact {
super.matrixId,
super.thirdPartyIdToHashMap,
}) : super(
thirdPartyIdType: 'msisdn',
thirdPartyIdType: ThirdPartyIdType.msisdn,
thirdPartyId: number.msisdnSanitizer(),
);

Expand All @@ -99,7 +40,7 @@ class FederationPhone extends FederationThirdPartyContact {

FederationPhone copyWith({
String? matrixId,
Map<String, String>? thirdPartyIdToHashMap,
Map<String, List<String>>? thirdPartyIdToHashMap,
}) {
return FederationPhone(
matrixId: matrixId ?? this.matrixId,
Expand All @@ -118,7 +59,7 @@ class FederationEmail extends FederationThirdPartyContact {
required this.address,
super.thirdPartyIdToHashMap,
}) : super(
thirdPartyIdType: 'email',
thirdPartyIdType: ThirdPartyIdType.email,
thirdPartyId: address,
);

Expand All @@ -132,7 +73,7 @@ class FederationEmail extends FederationThirdPartyContact {

FederationEmail copyWith({
String? matrixId,
Map<String, String>? thirdPartyIdToHashMap,
Map<String, List<String>>? thirdPartyIdToHashMap,
}) {
return FederationEmail(
matrixId: matrixId ?? this.matrixId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ class FederationIdentityCalculationHashesEmpty extends Failure {
List<Object?> get props => [];
}

class NoFederationIdentityURL extends Failure {
const NoFederationIdentityURL();

@override
List<Object?> get props => [];
}

class FederationIdentityRegisterAccountFailure extends Failure {
const FederationIdentityRegisterAccountFailure({
required this.identityServer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
import 'package:dartz/dartz.dart';
import 'package:fluffychat/app_state/failure.dart';
import 'package:fluffychat/app_state/success.dart';
import 'package:fluffychat/modules/federation_identity_lookup/domain/exceptions/federation_identity_lookup_exceptions.dart';
import 'package:fluffychat/modules/federation_identity_lookup/domain/models/federation_arguments.dart';
import 'package:fluffychat/modules/federation_identity_lookup/domain/models/federation_contact.dart';
import 'package:fluffychat/modules/federation_identity_lookup/domain/models/federation_lookup_mxid_request.dart';
Expand All @@ -20,11 +21,14 @@ class FederationIdentityLookupInteractor {
required FederationArguments arguments,
}) async {
try {
if (arguments.federationUrl.isEmpty) {
return const Left(NoFederationIdentityURL());
}

final registerResponse =
await federationIdentityLookupRepository.register(
tokenInformation: arguments.tokenInformation,
);

if (registerResponse.token == null && registerResponse.token!.isEmpty) {
return Left(
FederationIdentityRegisterAccountFailure(
Expand All @@ -37,7 +41,6 @@ class FederationIdentityLookupInteractor {
await federationIdentityLookupRepository.getHashDetails(
registeredToken: registerResponse.token!,
);

if (hashDetails.lookupPepper == null || hashDetails.algorithms == null) {
return const Left(FederationIdentityGetHashDetailsFailure());
}
Expand All @@ -47,40 +50,42 @@ class FederationIdentityLookupInteractor {
final Map<String, List<String>> contactIdToHashesMap = {};

for (final contact in arguments.contactMaps.values) {
final phoneToHashMap = <String, String>{};
final phoneToHashMap = <String, List<String>>{};

final emailToHashMap = <String, String>{};
final emailToHashMap = <String, List<String>>{};
if (hashDetails.algorithms!.isEmpty) {
return const Left(FederationIdentityGetHashDetailsFailure());
}

if (contact.phoneNumbers != null) {
for (final phone in contact.phoneNumbers!) {
final hashes = phone.calculateHashUsingAllPeppers(
hashDetails: hashDetails,
lookupPepper: hashDetails.lookupPepper,
altLookupPeppers: hashDetails.altLookupPeppers,
algorithms: hashDetails.algorithms,
);
for (final hash in hashes) {
phoneToHashMap.putIfAbsent(phone.number, () => hash);
}

contactIdToHashesMap.putIfAbsent(contact.id, () => []).addAll(
phoneToHashMap.values,
);
phoneToHashMap.putIfAbsent(phone.number, () => []).addAll(hashes);

contactIdToHashesMap
.putIfAbsent(contact.id, () => [])
.addAll(phoneToHashMap.values.expand((e) => e));
}
}

if (contact.emails != null) {
for (final email in contact.emails!) {
final hashes = email.calculateHashUsingAllPeppers(
hashDetails: hashDetails,
lookupPepper: hashDetails.lookupPepper,
altLookupPeppers: hashDetails.altLookupPeppers,
algorithms: hashDetails.algorithms,
);

for (final hash in hashes) {
emailToHashMap.putIfAbsent(email.address, () => hash);
}
contactIdToHashesMap.putIfAbsent(contact.id, () => []).addAll(
emailToHashMap.values,
);
emailToHashMap.putIfAbsent(email.address, () => []).addAll(hashes);

contactIdToHashesMap
.putIfAbsent(contact.id, () => [])
.addAll(emailToHashMap.values.expand((e) => e));
}
}

Expand Down Expand Up @@ -110,9 +115,11 @@ class FederationIdentityLookupInteractor {
registeredToken: registerResponse.token!,
);

if (lookupMxidResponse.mappings == null) {
return const Left(
FederationIdentityLookupFailure(exception: 'No mappings found'),
if (lookupMxidResponse.mappings == null || lookupMxidResponse.mappings!.isEmpty) {
return Left(
FederationIdentityLookupFailure(
exception: LookUpFederationIdentityNotFoundException('No mappings found'),
),
);
}

Expand Down Expand Up @@ -158,8 +165,8 @@ class FederationIdentityLookupInteractor {

FederationContact updateContactWithHashes(
FederationContact contact,
Map<String, String> phoneToHashMap,
Map<String, String> emailToHashMap,
Map<String, List<String>> phoneToHashMap,
Map<String, List<String>> emailToHashMap,
) {
final updatedPhoneNumbers = <FederationPhone>{};
final updatedEmails = <FederationEmail>{};
Expand Down Expand Up @@ -207,8 +214,9 @@ class FederationIdentityLookupInteractor {
final thirdPartyIdToHashMap = phoneNumber.thirdPartyIdToHashMap ?? {};

for (final mappingHash in thirdPartyIdToHashMap.values) {
final foundHash = mappings.keys.firstWhereOrNull(
(hash) => mappingHash == hash,
final foundHash = mappingHash.firstWhereOrNull(
(hash) =>
mappings.keys.firstWhereOrNull((key) => key == hash) != null,
);
if (foundHash != null) {
final updatedPhoneNumber = phoneNumber.copyWith(
Expand All @@ -229,8 +237,9 @@ class FederationIdentityLookupInteractor {
for (final email in contact.emails!) {
final thirdPartyIdToHashMap = email.thirdPartyIdToHashMap ?? {};
for (final mappingHash in thirdPartyIdToHashMap.values) {
final foundHash = mappings.keys.firstWhereOrNull(
(hash) => mappingHash == hash,
final foundHash = mappingHash.firstWhereOrNull(
(hash) =>
mappings.keys.firstWhereOrNull((key) => key == hash) != null,
);
if (foundHash != null) {
final updatedEmail = email.copyWith(
Expand Down
Loading

0 comments on commit 09d572a

Please sign in to comment.