diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index 1f6644d15..3a2e54dfc 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -1894,6 +1894,22 @@ 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 + // 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]; + + BOOL retVal = [migrationAssistant storeUsernameWithSession: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/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/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/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 new file mode 100644 index 000000000..6eb005f34 --- /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 storeUsername( session: MXSession ) -> Bool { + + var returnValue = false + + if let username = session.myUser.username() { + let ossStatus = SharedKeychain.save(account: "migration_username", value: String(username)) + returnValue = ossStatus == errSecSuccess + } + + return returnValue + } + + func storeHomeserver( session: MXSession ) -> Bool { + var returnValue = false + + 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 new file mode 100644 index 000000000..a0f908561 --- /dev/null +++ b/bwi/SharedKeychain/SharedKeychain.swift @@ -0,0 +1,89 @@ +// +/* + * 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 + SharedKeychain.delete(account: account) + + // 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) + } +}