From 3f124ef1ce52377623f65a8bca52ca8f46fc241a Mon Sep 17 00:00:00 2001 From: Frank Rotermund Date: Tue, 23 Sep 2025 16:00:30 +0200 Subject: [PATCH 1/3] feat: add displayname and homeserver migration to legacy (MESSENGER-7565) --- .../Recents/DataSources/RecentsDataSource.m | 15 +++ Riot/SupportingFiles/Info.plist | 2 + Riot/SupportingFiles/Riot.entitlements | 1 + .../MigrationAssistant.swift | 46 +++++++++ bwi/SharedKeychain/SharedKeychain.swift | 95 +++++++++++++++++++ 5 files changed, 159 insertions(+) create mode 100644 bwi/MigrationAssistent/MigrationAssistant.swift create mode 100644 bwi/SharedKeychain/SharedKeychain.swift diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index 1f6644d15..719ecb8c3 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -1894,6 +1894,21 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou MXSession* session = [self mxSession]; if(!session) return; + + // bwi #7565 as we have a session here and its a migration step -> use the migrationAssistant here + + MigrationAssistant *migrationAssistant = [[MigrationAssistant alloc] init]; + + BOOL retVal = [migrationAssistant storeDisplaynameWithSession:session]; + if ( retVal == false) { + MXLogError(@"[RecentsDataSource] shouldShowFeatureBanner could not store displaname for migration") + } + + retVal = [migrationAssistant storeHomeserverWithSession:session]; + if ( retVal == false) { + MXLogError(@"[RecentsDataSource] shouldShowFeatureBanner could not store homeserver for migration") + } + FeatureBannerVisibilityService *featureBannerService = [FeatureBannerVisibilityService alloc]; [featureBannerService isUnreadWithCompletion: ^(BOOL unread) { if (unread) { diff --git a/Riot/SupportingFiles/Info.plist b/Riot/SupportingFiles/Info.plist index 2157da520..480c9018e 100644 --- a/Riot/SupportingFiles/Info.plist +++ b/Riot/SupportingFiles/Info.plist @@ -149,5 +149,7 @@ $(BASE_BUNDLE_IDENTIFIER) keychainAccessGroup $(KEYCHAIN_ACCESS_GROUP) + KeychainMigrationSharedGroup + $(AppIdentifierPrefix)de.bwi.messenger.shared.migration diff --git a/Riot/SupportingFiles/Riot.entitlements b/Riot/SupportingFiles/Riot.entitlements index b7f9c6920..f9ab80917 100644 --- a/Riot/SupportingFiles/Riot.entitlements +++ b/Riot/SupportingFiles/Riot.entitlements @@ -13,6 +13,7 @@ keychain-access-groups $(KEYCHAIN_ACCESS_GROUP) + $(AppIdentifierPrefix)de.bwi.messenger.shared.migration diff --git a/bwi/MigrationAssistent/MigrationAssistant.swift b/bwi/MigrationAssistent/MigrationAssistant.swift new file mode 100644 index 000000000..2c8170cbb --- /dev/null +++ b/bwi/MigrationAssistent/MigrationAssistant.swift @@ -0,0 +1,46 @@ +// +/* + * Copyright (c) 2025 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation +import MatrixSDK + +// bwi #7565 has to be a class to be called by Objective C +// reads user name and displayName from session and stores in shared keychain + +@objcMembers class MigrationAssistant : NSObject { + func storeDisplayname( session: MXSession ) -> Bool { + + var returnValue = false + + if let displayname = session.myUser.displayName { + let ossStatus = SharedKeychain.save(account: "migration_displayname", value: displayname) + returnValue = ossStatus == errSecSuccess + } + + return returnValue + } + + func storeHomeserver( session: MXSession ) -> Bool { + var returnValue = false + + if let displayname = session.matrixRestClient.homeserver { + returnValue = SharedKeychain.save(account: "migration_homeserver", value: displayname) == errSecSuccess + } + + return returnValue + } +} diff --git a/bwi/SharedKeychain/SharedKeychain.swift b/bwi/SharedKeychain/SharedKeychain.swift new file mode 100644 index 000000000..9703c992f --- /dev/null +++ b/bwi/SharedKeychain/SharedKeychain.swift @@ -0,0 +1,95 @@ +// +/* + * Copyright (c) 2025 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Security +import Foundation + +enum SharedKeychainConfig { + static var sharedGroup: String { + Bundle.main.object(forInfoDictionaryKey: "KeychainMigrationSharedGroup") as? String + ?? "" + } + + static var service = "de.bwi.sharedkeychain" +} + +// bwi #7565 do some keychain magic, needs to have entitlement for SharedKeychainConfig.sharedGroup and needs to store it in info.plist to hide team id. + +enum SharedKeychain { + @discardableResult + static func save( + account: String, + value: String + ) -> OSStatus { + let data = value.data(using: .utf8)! + + // Delete existing item first + let deleteQuery: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: account, + kSecAttrService as String: SharedKeychainConfig.service, + kSecAttrAccessGroup as String: SharedKeychainConfig.sharedGroup + ] + SecItemDelete(deleteQuery as CFDictionary) + + // Add new value + let addQuery: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: account, + kSecAttrService as String: SharedKeychainConfig.service, + kSecAttrAccessGroup as String: SharedKeychainConfig.sharedGroup, + kSecValueData as String: data + ] + return SecItemAdd(addQuery as CFDictionary, nil) + } + + static func load( + account: String + ) -> String? { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: account, + kSecAttrService as String: SharedKeychainConfig.service, + kSecAttrAccessGroup as String: SharedKeychainConfig.sharedGroup, + kSecMatchLimit as String: kSecMatchLimitOne, + kSecReturnData as String: true + ] + + var result: AnyObject? + let status = SecItemCopyMatching(query as CFDictionary, &result) + + guard status == errSecSuccess, + let data = result as? Data, + let value = String(data: data, encoding: .utf8) else { + return nil + } + return value + } + + @discardableResult + static func delete( + account: String + ) -> OSStatus { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: account, + kSecAttrService as String: SharedKeychainConfig.service, + kSecAttrAccessGroup as String: SharedKeychainConfig.sharedGroup + ] + return SecItemDelete(query as CFDictionary) + } +} From be743b6b23129a8108c4ec2cff874978840dac25 Mon Sep 17 00:00:00 2001 From: Frank Rotermund Date: Wed, 24 Sep 2025 08:22:14 +0200 Subject: [PATCH 2/3] feat: use username instead of displayname (MESSENGER-7565) --- .../Common/Recents/DataSources/RecentsDataSource.m | 2 +- bwi/MigrationAssistent/MigrationAssistant.swift | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index 719ecb8c3..b696e5681 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -1899,7 +1899,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou MigrationAssistant *migrationAssistant = [[MigrationAssistant alloc] init]; - BOOL retVal = [migrationAssistant storeDisplaynameWithSession:session]; + BOOL retVal = [migrationAssistant storeUsernameWithSession:session]; if ( retVal == false) { MXLogError(@"[RecentsDataSource] shouldShowFeatureBanner could not store displaname for migration") } diff --git a/bwi/MigrationAssistent/MigrationAssistant.swift b/bwi/MigrationAssistent/MigrationAssistant.swift index 2c8170cbb..4ea9e8222 100644 --- a/bwi/MigrationAssistent/MigrationAssistant.swift +++ b/bwi/MigrationAssistent/MigrationAssistant.swift @@ -22,13 +22,16 @@ import MatrixSDK // reads user name and displayName from session and stores in shared keychain @objcMembers class MigrationAssistant : NSObject { - func storeDisplayname( session: MXSession ) -> Bool { + func storeUsername( session: MXSession ) -> Bool { var returnValue = false - if let displayname = session.myUser.displayName { - let ossStatus = SharedKeychain.save(account: "migration_displayname", value: displayname) - returnValue = ossStatus == errSecSuccess + if let fullID = session.myUser.userId { + let withoutAt = fullID.hasPrefix("@") ? String(fullID.dropFirst()) : fullID + if let username = withoutAt.split(separator: ":").first { + let ossStatus = SharedKeychain.save(account: "migration_username", value: String(username)) + returnValue = ossStatus == errSecSuccess + } } return returnValue From 515c286bd8efb8c6c607339798cb97b3f2673baa Mon Sep 17 00:00:00 2001 From: Frank Rotermund Date: Thu, 25 Sep 2025 14:12:00 +0200 Subject: [PATCH 3/3] feat: small fixes (MESSENGER-7565) --- .../Recents/DataSources/RecentsDataSource.m | 1 + .../AllChats/AllChatsViewController.swift | 5 +-- bwi/Extentions/MXUser+Username.swift | 34 +++++++++++++++++++ .../MigrationAssistant.swift | 13 +++---- bwi/SharedKeychain/SharedKeychain.swift | 8 +---- 5 files changed, 44 insertions(+), 17 deletions(-) create mode 100644 bwi/Extentions/MXUser+Username.swift diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index b696e5681..3a2e54dfc 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -1896,6 +1896,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou return; // bwi #7565 as we have a session here and its a migration step -> use the migrationAssistant here + // this is called everytime when migration level is >= 1 we should find a better place when level 2 and 3 are implemented MigrationAssistant *migrationAssistant = [[MigrationAssistant alloc] init]; diff --git a/Riot/Modules/Home/AllChats/AllChatsViewController.swift b/Riot/Modules/Home/AllChats/AllChatsViewController.swift index 3e4b3fcc7..ff5901520 100644 --- a/Riot/Modules/Home/AllChats/AllChatsViewController.swift +++ b/Riot/Modules/Home/AllChats/AllChatsViewController.swift @@ -622,8 +622,9 @@ class AllChatsViewController: HomeViewController { // bwi: feature banner cell if sectionType == .featureBanner { var username = "" - if let mainSession = self.mainSession { - username = mainSession.myUser.displayname + if let myUsername = self.mainSession.myUser.username() { + // bwi 7565 change to userID + username = myUsername } guard let cell = tableView.dequeueReusableCell(withIdentifier: "featureBanner", for: indexPath) as? FeatureBannerViewCell else { return UITableViewCell() diff --git a/bwi/Extentions/MXUser+Username.swift b/bwi/Extentions/MXUser+Username.swift new file mode 100644 index 000000000..408a8851f --- /dev/null +++ b/bwi/Extentions/MXUser+Username.swift @@ -0,0 +1,34 @@ +// +/* + * Copyright (c) 2025 BWI GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation +import MatrixSDK + + +// bwi #7576 username is used in several places, create a helper +// we need only the username part of the matrix id +extension MXUser { + func username () -> String? { + if let fullID = self.userId { + let withoutAt = fullID.hasPrefix("@") ? String(fullID.dropFirst()) : fullID + if let username = withoutAt.split(separator: ":").first { + return String(username) + } + } + return nil + } +} diff --git a/bwi/MigrationAssistent/MigrationAssistant.swift b/bwi/MigrationAssistent/MigrationAssistant.swift index 4ea9e8222..6eb005f34 100644 --- a/bwi/MigrationAssistent/MigrationAssistant.swift +++ b/bwi/MigrationAssistent/MigrationAssistant.swift @@ -26,12 +26,9 @@ import MatrixSDK var returnValue = false - if let fullID = session.myUser.userId { - let withoutAt = fullID.hasPrefix("@") ? String(fullID.dropFirst()) : fullID - if let username = withoutAt.split(separator: ":").first { - let ossStatus = SharedKeychain.save(account: "migration_username", value: String(username)) - returnValue = ossStatus == errSecSuccess - } + if let username = session.myUser.username() { + let ossStatus = SharedKeychain.save(account: "migration_username", value: String(username)) + returnValue = ossStatus == errSecSuccess } return returnValue @@ -40,8 +37,8 @@ import MatrixSDK func storeHomeserver( session: MXSession ) -> Bool { var returnValue = false - if let displayname = session.matrixRestClient.homeserver { - returnValue = SharedKeychain.save(account: "migration_homeserver", value: displayname) == errSecSuccess + if let homeserver = session.matrixRestClient.homeserver { + returnValue = SharedKeychain.save(account: "migration_homeserver", value: homeserver) == errSecSuccess } return returnValue diff --git a/bwi/SharedKeychain/SharedKeychain.swift b/bwi/SharedKeychain/SharedKeychain.swift index 9703c992f..a0f908561 100644 --- a/bwi/SharedKeychain/SharedKeychain.swift +++ b/bwi/SharedKeychain/SharedKeychain.swift @@ -38,13 +38,7 @@ enum SharedKeychain { let data = value.data(using: .utf8)! // Delete existing item first - let deleteQuery: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrAccount as String: account, - kSecAttrService as String: SharedKeychainConfig.service, - kSecAttrAccessGroup as String: SharedKeychainConfig.sharedGroup - ] - SecItemDelete(deleteQuery as CFDictionary) + SharedKeychain.delete(account: account) // Add new value let addQuery: [String: Any] = [