Skip to content

Commit

Permalink
feat: Create an MLS client at the end of the quick sync, if needed - …
Browse files Browse the repository at this point in the history
…WPB-14987 (#2281)
  • Loading branch information
KaterinaWire authored Dec 15, 2024
1 parent 4c6a8fb commit f39bc3d
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -931,28 +931,45 @@ extension ZMUserSession: ZMSyncStateDelegate {
context: notificationContext
).post()

let selfClient = ZMUser.selfUser(in: syncContext).selfClient()
if selfClient?.hasRegisteredMLSClient == true {

WaitingGroupTask(context: syncContext) { [self] in
// these operations are not dependent and should not be executed in same do/catch
do {
// rework implementation of following method - WPB-6053
try await mlsService.performPendingJoins()
} catch {
WireLogger.mls.error("Failed to performPendingJoins: \(String(reflecting: error))")
}
await mlsService.uploadKeyPackagesIfNeeded()
await mlsService.updateKeyMaterialForAllStaleGroupsIfNeeded()
func performsMLSClientUpdates() async {
// these operations are not dependent and should not be executed in same do/catch
do {
// rework implementation of following method - WPB-6053
try await mlsService.performPendingJoins()
} catch {
WireLogger.mls.error("Failed to performPendingJoins: \(String(reflecting: error))")
}
}

if mlsFeature.isEnabled {
mlsService.commitPendingProposalsIfNeeded()
await mlsService.uploadKeyPackagesIfNeeded()
await mlsService.updateKeyMaterialForAllStaleGroupsIfNeeded()
}

WaitingGroupTask(context: syncContext) { [self] in
await fetchBackendMLSPublicKeys()
await fetchAndStoreFeatureConfig()

let (qualifiedSelfClientID, hasRegisteredMLSClient) = await syncContext.perform {
let selfClient = ZMUser.selfUser(in: self.syncContext).selfClient()
let hasRegisteredMLSClient = selfClient?.hasRegisteredMLSClient == true
return (selfClient?.qualifiedClientID, hasRegisteredMLSClient)
}

if hasRegisteredMLSClient {
await performsMLSClientUpdates()
} else {
// If we discover that
// there are MLS public keys on the backend, the MLS feature is enabled and there is no registered MLS client,
// we should create one.
let needsToRegisterMLSClient = BackendInfo.isMLSEnabled && mlsFeature.isEnabled
if let qualifiedSelfClientID, needsToRegisterMLSClient {
await createMLSClient(qualifiedID: qualifiedSelfClientID)
await performsMLSClientUpdates()
}
}

if mlsFeature.isEnabled {
mlsService.commitPendingProposalsIfNeeded()
}

await calculateSelfSupportedProtocolsIfNeeded()
await resolveOneOnOneConversationsIfNeeded()
}
Expand Down Expand Up @@ -1020,6 +1037,28 @@ extension ZMUserSession: ZMSyncStateDelegate {
}
}

private func fetchBackendMLSPublicKeys() async {
do {
var getBackendMLSPublicKeysAction = FetchBackendMLSPublicKeysAction()
let backendPublicKeys = try await getBackendMLSPublicKeysAction.perform(in: notificationContext)
let hasValidKeys = backendPublicKeys.removal.hasValidKeys()
BackendInfo.isMLSEnabled = hasValidKeys
} catch {
WireLogger.mls.info("Backend doesn't have MLS public keys: \(String(reflecting: error))")
}
}

private func createMLSClient(qualifiedID: QualifiedClientID) async {
let mlsClientID = await syncContext.perform {
return MLSClientID(qualifiedClientID: qualifiedID)
}
do {
try await self.coreCryptoProvider.initialiseMLSWithBasicCredentials(mlsClientID: mlsClientID)
} catch {
WireLogger.mls.error("Failed to initialise mls client: \(error)")
}
}

func processEvents() {
managedObjectContext.performGroupedBlock { [weak self] in
self?.isPerformingSync = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,11 @@ final class ZMUserSessionTests: ZMUserSessionTestsBase {
result: .success(()),
context: syncMOC.notificationContext
)
let backendPublicKeys = BackendMLSPublicKeys(removal: .init(ed25519: .init([1, 2, 3])))
let fetchBackendMLSPublicKeysActionHandler = MockActionHandler<FetchBackendMLSPublicKeysAction>(
result: .success(backendPublicKeys),
context: syncMOC.notificationContext
)

// MLS client has been registered
syncMOC.performAndWait {
Expand All @@ -494,6 +499,52 @@ final class ZMUserSessionTests: ZMUserSessionTestsBase {
XCTAssertEqual(mockRecurringActionService.performActionsIfNeeded_Invocations.count, 1)

XCTAssertEqual(getFeatureConfigsActionHandler.performedActions.count, 1)
XCTAssertEqual(fetchBackendMLSPublicKeysActionHandler.performedActions.count, 1)
}

func test_itCreatesMLSClientIfNeeded_AfterQuickSync() {
// GIVEN
syncMOC.performAndWait {
ZMUser.selfUser(in: self.syncMOC).domain = "anta.com"
}
let selfUserClient = syncMOC.performAndWait {
self.createSelfClient()
}

mockMLSService.performPendingJoins_MockMethod = {}
mockMLSService.commitPendingProposalsIfNeeded_MockMethod = {}
mockMLSService.uploadKeyPackagesIfNeeded_MockMethod = {}
mockMLSService.updateKeyMaterialForAllStaleGroupsIfNeeded_MockMethod = {}

syncMOC.performAndWait {
XCTAssertTrue(selfUserClient.mlsPublicKeys.isEmpty)

XCTAssertFalse(BackendInfo.isMLSEnabled)
XCTAssertFalse(sut.featureRepository.fetchMLS().isEnabled)
}

// WHEN
let backendPublicKeys = BackendMLSPublicKeys(removal: .init(ed25519: .init([1, 2, 3])))
let fetchBackendMLSPublicKeysActionHandler = MockActionHandler<FetchBackendMLSPublicKeysAction>(
result: .success(backendPublicKeys),
context: syncMOC.notificationContext
)
syncMOC.performAndWait {
let mls = Feature.MLS(status: .enabled, config: .init())
self.sut.featureRepository.storeMLS(mls)

sut.didFinishQuickSync()
}

XCTAssertTrue(waitForAllGroupsToBeEmpty(withTimeout: 0.5))

// THEN
syncMOC.performAndWait {
XCTAssertFalse(selfUserClient.mlsPublicKeys.isEmpty)

XCTAssertTrue(BackendInfo.isMLSEnabled)
XCTAssertTrue(sut.featureRepository.fetchMLS().isEnabled)
}
}

func test_didFinishQuickSync_CalculateSupportedProtocolsIfNoProtocols() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class ZMUserSessionTestsBase: MessagingTest {
var dataChangeNotificationsCount: UInt = 0
var mockSyncStateDelegate: MockSyncStateDelegate!
var mockGetFeatureConfigsActionHandler: MockActionHandler<GetFeatureConfigsAction>!
var mockFetchBackendMLSPublicKeysActionHandler: MockActionHandler<FetchBackendMLSPublicKeysAction>!
var mockRecurringActionService: MockRecurringActionServiceInterface!

var sut: ZMUserSession!
Expand All @@ -49,6 +50,8 @@ class ZMUserSessionTestsBase: MessagingTest {
WireCallCenterV3Factory.wireCallCenterClass = WireCallCenterV3Mock.self

mockGetFeatureConfigsActionHandler = .init(result: .success(()), context: syncMOC.notificationContext)
let backendPublicKeys = BackendMLSPublicKeys(removal: .init(ed25519: .init([1, 2, 3])))
mockFetchBackendMLSPublicKeysActionHandler = .init(result: .success(backendPublicKeys), context: syncMOC.notificationContext)

dataChangeNotificationsCount = 0
baseURL = URL(string: "http://bar.example.com")
Expand Down Expand Up @@ -106,6 +109,7 @@ class ZMUserSessionTestsBase: MessagingTest {
let sut = sut
self.sut = nil
mockGetFeatureConfigsActionHandler = nil
mockFetchBackendMLSPublicKeysActionHandler = nil
sut?.tearDown()

super.tearDown()
Expand Down
1 change: 1 addition & 0 deletions wire-ios/Tests/TestPlans/AllTests.xctestplan
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"CallActionsViewSnapshotTests",
"MessageReplyPreviewViewTests\/testThatItRendersImageMessagePreview()",
"SearchResultLabelTests",
"ServiceDetailViewControllerSnapshotTests\/testForTeamMemberWrappedInNavigationController()",
"ShareViewControllerTests\/testThatItRendersCorrectlyShareViewController_Photos()",
"ShareViewControllerTests\/testThatItRendersCorrectlyShareViewController_Video_DarkMode()",
"StartUIViewControllerSnapshotTests\/testForNoContactWhenSelfIsTeamMember()"
Expand Down

0 comments on commit f39bc3d

Please sign in to comment.