Enable OIDC token refresh in the NSE. (#1711)

Update the SDK and handle API changes.
This commit is contained in:
Doug
2023-09-15 10:01:09 +01:00
committed by GitHub
parent 058fe79f25
commit 64d5ba7a69
19 changed files with 64 additions and 52 deletions
+1 -1
View File
@@ -5645,7 +5645,7 @@
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift"; repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift";
requirement = { requirement = {
kind = exactVersion; kind = exactVersion;
version = 1.1.13; version = 1.1.14;
}; };
}; };
821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = { 821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = {
@@ -129,8 +129,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/matrix-org/matrix-rust-components-swift", "location" : "https://github.com/matrix-org/matrix-rust-components-swift",
"state" : { "state" : {
"revision" : "482c8c04019e6e6ac9638ae792adae9d67b08114", "revision" : "db9a4c6eddc385c62695afdf7562827c7a586e72",
"version" : "1.1.13" "version" : "1.1.14"
} }
}, },
{ {
@@ -217,7 +217,7 @@
{ {
"identity" : "swiftui-introspect", "identity" : "swiftui-introspect",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/siteline/SwiftUI-Introspect.git", "location" : "https://github.com/siteline/SwiftUI-Introspect",
"state" : { "state" : {
"revision" : "b94da693e57eaf79d16464b8b7c90d09cba4e290", "revision" : "b94da693e57eaf79d16464b8b7c90d09cba4e290",
"version" : "0.9.2" "version" : "0.9.2"
@@ -527,8 +527,6 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
switch callback { switch callback {
case .didReceiveAuthError(let isSoftLogout): case .didReceiveAuthError(let isSoftLogout):
stateMachine.processEvent(.signOut(isSoft: isSoftLogout)) stateMachine.processEvent(.signOut(isSoft: isSoftLogout))
case .updateRestorationToken:
userSessionStore.refreshRestorationToken(for: userSession)
default: default:
break break
} }
@@ -44,7 +44,9 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
passphrase: nil, passphrase: nil,
userAgent: UserAgentBuilder.makeASCIIUserAgent(), userAgent: UserAgentBuilder.makeASCIIUserAgent(),
oidcConfiguration: oidcConfiguration, oidcConfiguration: oidcConfiguration,
customSlidingSyncProxy: appSettings.slidingSyncProxyURL?.absoluteString) customSlidingSyncProxy: appSettings.slidingSyncProxyURL?.absoluteString,
sessionDelegate: userSessionStore.clientSessionDelegate,
crossProcessRefreshLockId: InfoPlistReader.main.bundleIdentifier)
} }
// MARK: - Public // MARK: - Public
@@ -86,8 +86,6 @@ class ClientProxy: ClientProxyProtocol {
client.setDelegate(delegate: ClientDelegateWrapper { [weak self] isSoftLogout in client.setDelegate(delegate: ClientDelegateWrapper { [weak self] isSoftLogout in
self?.hasEncounteredAuthError = true self?.hasEncounteredAuthError = true
self?.callbacks.send(.receivedAuthError(isSoftLogout: isSoftLogout)) self?.callbacks.send(.receivedAuthError(isSoftLogout: isSoftLogout))
} tokenRefreshCallback: { [weak self] in
self?.callbacks.send(.updateRestorationToken)
}) })
await configureAppService() await configureAppService()
@@ -536,12 +534,9 @@ private class RoomListServiceSyncIndicatorListenerProxy: RoomListServiceSyncIndi
private class ClientDelegateWrapper: ClientDelegate { private class ClientDelegateWrapper: ClientDelegate {
private let authErrorCallback: (Bool) -> Void private let authErrorCallback: (Bool) -> Void
private let tokenRefreshCallback: () -> Void
init(authErrorCallback: @escaping (Bool) -> Void, init(authErrorCallback: @escaping (Bool) -> Void) {
tokenRefreshCallback: @escaping () -> Void) {
self.authErrorCallback = authErrorCallback self.authErrorCallback = authErrorCallback
self.tokenRefreshCallback = tokenRefreshCallback
} }
// MARK: - ClientDelegate // MARK: - ClientDelegate
@@ -552,7 +547,6 @@ private class ClientDelegateWrapper: ClientDelegate {
} }
func didRefreshTokens() { func didRefreshTokens() {
MXLog.info("The session has updated tokens.") MXLog.info("Delegating session updates to the ClientSessionDelegate.")
tokenRefreshCallback()
} }
} }
@@ -21,7 +21,6 @@ import MatrixRustSDK
enum ClientProxyCallback { enum ClientProxyCallback {
case receivedSyncUpdate case receivedSyncUpdate
case receivedAuthError(isSoftLogout: Bool) case receivedAuthError(isSoftLogout: Bool)
case updateRestorationToken
var isSyncUpdate: Bool { var isSyncUpdate: Bool {
if case .receivedSyncUpdate = self { if case .receivedSyncUpdate = self {
@@ -16,6 +16,7 @@
import Foundation import Foundation
import KeychainAccess import KeychainAccess
import MatrixRustSDK
enum KeychainControllerService: String { enum KeychainControllerService: String {
case sessions case sessions
@@ -86,4 +87,20 @@ class KeychainController: KeychainControllerProtocol {
MXLog.error("Failed removing all tokens") MXLog.error("Failed removing all tokens")
} }
} }
// MARK: - ClientSessionDelegate
func retrieveSessionFromKeychain(userId: String) throws -> Session {
MXLog.info("Retrieving an updated Session from the keychain.")
guard let session = restorationTokenForUsername(userId)?.session else {
throw ClientError.Generic(msg: "Failed to find RestorationToken in the Keychain.")
}
return session
}
func saveSessionInKeychain(session: Session) {
MXLog.info("Saving session changes in the keychain.")
let restorationToken = RestorationToken(session: session)
setRestorationToken(restorationToken, forUsername: session.userId)
}
} }
@@ -15,13 +15,14 @@
// //
import Foundation import Foundation
import MatrixRustSDK
struct KeychainCredentials { struct KeychainCredentials {
let userID: String let userID: String
let restorationToken: RestorationToken let restorationToken: RestorationToken
} }
protocol KeychainControllerProtocol { protocol KeychainControllerProtocol: ClientSessionDelegate {
func setRestorationToken(_ restorationToken: RestorationToken, forUsername: String) func setRestorationToken(_ restorationToken: RestorationToken, forUsername: String)
func restorationTokenForUsername(_ username: String) -> RestorationToken? func restorationTokenForUsername(_ username: String) -> RestorationToken?
func restorationTokens() -> [KeychainCredentials] func restorationTokens() -> [KeychainCredentials]
@@ -65,7 +65,7 @@ extension NotificationItemProxyProtocol {
return false return false
case let .messageLike(content): case let .messageLike(content):
switch content { switch content {
case let .roomMessage(messageType): case let .roomMessage(messageType, _):
switch messageType { switch messageType {
case .image, .video, .audio: case .image, .video, .audio:
return true return true
@@ -229,7 +229,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
inviterProxy = RoomMemberProxy(member: inviter, backgroundTaskService: backgroundTaskService) inviterProxy = RoomMemberProxy(member: inviter, backgroundTaskService: backgroundTaskService)
} }
let notificationMode = roomInfo.notificationMode.flatMap { RoomNotificationModeProxy.from(roomNotificationMode: $0) } let notificationMode = roomInfo.userDefinedNotificationMode.flatMap { RoomNotificationModeProxy.from(roomNotificationMode: $0) }
let details = RoomSummaryDetails(id: roomInfo.id, let details = RoomSummaryDetails(id: roomInfo.id,
name: roomInfo.name ?? roomInfo.id, name: roomInfo.name ?? roomInfo.id,
@@ -94,8 +94,6 @@ class UserSession: UserSessionProtocol {
case .receivedAuthError(let isSoftLogout): case .receivedAuthError(let isSoftLogout):
callbacks.send(.didReceiveAuthError(isSoftLogout: isSoftLogout)) callbacks.send(.didReceiveAuthError(isSoftLogout: isSoftLogout))
tearDownAuthErrorWatchdog() tearDownAuthErrorWatchdog()
case .updateRestorationToken:
callbacks.send(.updateRestorationToken)
default: default:
break break
} }
@@ -21,7 +21,6 @@ enum UserSessionCallback {
case sessionVerificationNeeded case sessionVerificationNeeded
case didVerifySession case didVerifySession
case didReceiveAuthError(isSoftLogout: Bool) case didReceiveAuthError(isSoftLogout: Bool)
case updateRestorationToken
} }
protocol UserSessionProtocol { protocol UserSessionProtocol {
@@ -31,6 +31,8 @@ class UserSessionStore: UserSessionStoreProtocol {
/// The base directory where all session data is stored. /// The base directory where all session data is stored.
let baseDirectory: URL let baseDirectory: URL
var clientSessionDelegate: ClientSessionDelegate { keychainController }
init(backgroundTaskService: BackgroundTaskServiceProtocol) { init(backgroundTaskService: BackgroundTaskServiceProtocol) {
keychainController = KeychainController(service: .sessions, keychainController = KeychainController(service: .sessions,
accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier) accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier)
@@ -76,16 +78,6 @@ class UserSessionStore: UserSessionStoreProtocol {
return .failure(error) return .failure(error)
} }
} }
func refreshRestorationToken(for userSession: UserSessionProtocol) -> Result<Void, UserSessionStoreError> {
guard let restorationToken = userSession.clientProxy.restorationToken else {
return .failure(.failedRefreshingRestoreToken)
}
keychainController.setRestorationToken(restorationToken, forUsername: userSession.clientProxy.userID)
return .success(())
}
func logout(userSession: UserSessionProtocol) { func logout(userSession: UserSessionProtocol) {
let userID = userSession.clientProxy.userID let userID = userSession.clientProxy.userID
@@ -115,6 +107,8 @@ class UserSessionStore: UserSessionStoreProtocol {
.username(username: credentials.userID) .username(username: credentials.userID)
.homeserverUrl(url: credentials.restorationToken.session.homeserverUrl) .homeserverUrl(url: credentials.restorationToken.session.homeserverUrl)
.userAgent(userAgent: UserAgentBuilder.makeASCIIUserAgent()) .userAgent(userAgent: UserAgentBuilder.makeASCIIUserAgent())
.enableCrossProcessRefreshLock(processId: InfoPlistReader.main.bundleIdentifier,
sessionDelegate: keychainController)
.serverVersions(versions: ["v1.0", "v1.1", "v1.2", "v1.3", "v1.4", "v1.5"]) // FIXME: Quick and dirty fix for stopping version requests on startup https://github.com/matrix-org/matrix-rust-sdk/pull/1376 .serverVersions(versions: ["v1.0", "v1.1", "v1.2", "v1.3", "v1.4", "v1.5"]) // FIXME: Quick and dirty fix for stopping version requests on startup https://github.com/matrix-org/matrix-rust-sdk/pull/1376
do { do {
@@ -133,9 +127,9 @@ class UserSessionStore: UserSessionStoreProtocol {
private func setupProxyForClient(_ client: Client) async -> Result<ClientProxyProtocol, UserSessionStoreError> { private func setupProxyForClient(_ client: Client) async -> Result<ClientProxyProtocol, UserSessionStoreError> {
do { do {
let session = try client.session() let session = try client.session()
let userId = try client.userId() let userID = try client.userId()
keychainController.setRestorationToken(RestorationToken(session: session), forUsername: userId) keychainController.setRestorationToken(RestorationToken(session: session), forUsername: userID)
} catch { } catch {
MXLog.error("Failed setting up user session with error: \(error)") MXLog.error("Failed setting up user session with error: \(error)")
return .failure(.failedSettingUpSession) return .failure(.failedSettingUpSession)
@@ -37,15 +37,14 @@ protocol UserSessionStoreProtocol {
/// Returns the location to store user data for a particular username. /// Returns the location to store user data for a particular username.
var baseDirectory: URL { get } var baseDirectory: URL { get }
/// Returns the delegate that should handle any changes to a `Client`'s `Session`.
var clientSessionDelegate: ClientSessionDelegate { get }
/// Restores an existing user session. /// Restores an existing user session.
func restoreUserSession() async -> Result<UserSessionProtocol, UserSessionStoreError> func restoreUserSession() async -> Result<UserSessionProtocol, UserSessionStoreError>
/// Creates a user session for a new client from the SDK. /// Creates a user session for a new client from the SDK.
func userSession(for client: Client) async -> Result<UserSessionProtocol, UserSessionStoreError> func userSession(for client: Client) async -> Result<UserSessionProtocol, UserSessionStoreError>
/// Refresh the restore token of the client for a given session.
@discardableResult
func refreshRestorationToken(for userSession: UserSessionProtocol) -> Result<Void, UserSessionStoreError>
/// Logs out of the specified session. /// Logs out of the specified session.
func logout(userSession: UserSessionProtocol) func logout(userSession: UserSessionProtocol)
+1 -1
View File
@@ -36,7 +36,7 @@ struct NotificationContentBuilder {
switch try? event.eventType() { switch try? event.eventType() {
case let .messageLike(content): case let .messageLike(content):
switch content { switch content {
case .roomMessage(messageType: let messageType): case .roomMessage(let messageType, _):
return try await processRoomMessage(notificationItem: notificationItem, messageType: messageType, mediaProvider: mediaProvider) return try await processRoomMessage(notificationItem: notificationItem, messageType: messageType, mediaProvider: mediaProvider)
default: default:
return processEmpty(notificationItem: notificationItem) return processEmpty(notificationItem: notificationItem)
@@ -41,12 +41,6 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
// - NotificationID could not be resolved // - NotificationID could not be resolved
return contentHandler(request.content) return contentHandler(request.content)
} }
if credentials.restorationToken.session.oidcData != nil {
// Notification content is disabled for OIDC sessions
// until token refresh is multi-process aware.
return contentHandler(request.content)
}
handler = contentHandler handler = contentHandler
modifiedContent = request.content.mutableCopy() as? UNMutableNotificationContent modifiedContent = request.content.mutableCopy() as? UNMutableNotificationContent
@@ -80,8 +74,9 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
MXLog.info("\(tag) run with roomId: \(roomId), eventId: \(eventId)") MXLog.info("\(tag) run with roomId: \(roomId), eventId: \(eventId)")
do { do {
let userSession = try NSEUserSession(credentials: credentials) let userSession = try NSEUserSession(credentials: credentials, clientSessionDelegate: keychainController)
self.userSession = userSession self.userSession = userSession
guard let itemProxy = await userSession.notificationItemProxy(roomID: roomId, eventID: eventId) else { guard let itemProxy = await userSession.notificationItemProxy(roomID: roomId, eventID: eventId) else {
MXLog.info("\(tag) no notification for the event, discard") MXLog.info("\(tag) no notification for the event, discard")
return discard() return discard()
+20 -5
View File
@@ -25,27 +25,30 @@ final class NSEUserSession {
imageCache: .onlyOnDisk, imageCache: .onlyOnDisk,
backgroundTaskService: nil) backgroundTaskService: nil)
init(credentials: KeychainCredentials) throws { init(credentials: KeychainCredentials, clientSessionDelegate: ClientSessionDelegate) throws {
userID = credentials.userID userID = credentials.userID
baseClient = try ClientBuilder() baseClient = try ClientBuilder()
.basePath(path: URL.sessionsBaseDirectory.path) .basePath(path: URL.sessionsBaseDirectory.path)
.username(username: credentials.userID) .username(username: credentials.userID)
.userAgent(userAgent: UserAgentBuilder.makeASCIIUserAgent()) .userAgent(userAgent: UserAgentBuilder.makeASCIIUserAgent())
.enableCrossProcessRefreshLock(processId: InfoPlistReader.main.bundleIdentifier,
sessionDelegate: clientSessionDelegate)
.build() .build()
baseClient.setDelegate(delegate: ClientDelegateWrapper())
try baseClient.restoreSession(session: credentials.restorationToken.session) try baseClient.restoreSession(session: credentials.restorationToken.session)
notificationClient = try baseClient notificationClient = try baseClient
.notificationClient(processSetup: .multipleProcesses) .notificationClient(processSetup: .multipleProcesses)
.filterByPushRules() .filterByPushRules()
.finish() .finish()
} }
func notificationItemProxy(roomID: String, eventID: String) async -> NotificationItemProxyProtocol? { func notificationItemProxy(roomID: String, eventID: String) async -> NotificationItemProxyProtocol? {
await Task.dispatch(on: .global()) { await Task.dispatch(on: .global()) {
do { do {
let notification = try self.notificationClient.getNotification(roomId: roomID, eventId: eventID) let notification = try self.notificationClient.getNotification(roomId: roomID, eventId: eventID)
guard let notification else { guard let notification else {
return nil return nil
} }
@@ -60,3 +63,15 @@ final class NSEUserSession {
} }
} }
} }
private class ClientDelegateWrapper: ClientDelegate {
// MARK: - ClientDelegate
func didReceiveAuthError(isSoftLogout: Bool) {
MXLog.error("Received authentication error, the NSE can't handle this.")
}
func didRefreshTokens() {
MXLog.info("Delegating session updates to the ClientSessionDelegate.")
}
}
+1
View File
@@ -0,0 +1 @@
Enable token refresh in the NSE (and notifications for OIDC accounts).
+1 -1
View File
@@ -46,7 +46,7 @@ packages:
# Element/Matrix dependencies # Element/Matrix dependencies
MatrixRustSDK: MatrixRustSDK:
url: https://github.com/matrix-org/matrix-rust-components-swift url: https://github.com/matrix-org/matrix-rust-components-swift
exactVersion: 1.1.13 exactVersion: 1.1.14
# path: ../matrix-rust-sdk # path: ../matrix-rust-sdk
DesignKit: DesignKit:
path: DesignKit path: DesignKit