Adopt Rust's new sessionPath that replaces the basePath (#2903)

This commit is contained in:
Doug
2024-06-06 18:35:57 +01:00
committed by GitHub
parent 660b8243bc
commit 2487895207
17 changed files with 150 additions and 113 deletions
@@ -21,19 +21,21 @@ import MatrixRustSDK
class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
private let authenticationService: AuthenticationService
private let userSessionStore: UserSessionStoreProtocol
private let passphrase: String?
private let sessionDirectory: URL
private let passphrase: String
private let homeserverSubject: CurrentValueSubject<LoginHomeserver, Never>
var homeserver: CurrentValuePublisher<LoginHomeserver, Never> { homeserverSubject.asCurrentValuePublisher() }
init(userSessionStore: UserSessionStoreProtocol, encryptionKeyProvider: EncryptionKeyProviderProtocol, appSettings: AppSettings) {
sessionDirectory = .sessionsBaseDirectory.appending(component: UUID().uuidString)
passphrase = encryptionKeyProvider.generateKey().base64EncodedString()
self.userSessionStore = userSessionStore
homeserverSubject = .init(LoginHomeserver(address: appSettings.defaultHomeserverAddress,
loginMode: .unknown))
authenticationService = AuthenticationService(basePath: userSessionStore.baseDirectory.path,
authenticationService = AuthenticationService(sessionPath: sessionDirectory.path(percentEncoded: false),
passphrase: passphrase,
userAgent: UserAgentBuilder.makeASCIIUserAgent(),
additionalRootCertificates: [],
@@ -136,7 +138,7 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol {
// MARK: - Private
private func userSession(for client: Client) async -> Result<UserSessionProtocol, AuthenticationServiceError> {
switch await userSessionStore.userSession(for: client, passphrase: passphrase) {
switch await userSessionStore.userSession(for: client, sessionDirectory: sessionDirectory, passphrase: passphrase) {
case .success(let clientProxy):
return .success(clientProxy)
case .failure:
@@ -25,6 +25,7 @@ struct OIDCConfigurationProxy {
let policyURI: URL
let contacts: [String]
let staticRegistrations: [String: String]
let dynamicRegistrationsFile: URL
}
#if canImport(MatrixRustSDK)
@@ -39,7 +40,8 @@ extension OIDCConfigurationProxy {
tosUri: tosURI.absoluteString,
policyUri: policyURI.absoluteString,
contacts: contacts,
staticRegistrations: staticRegistrations)
staticRegistrations: staticRegistrations,
dynamicRegistrationsFile: dynamicRegistrationsFile.path(percentEncoded: false))
}
}
#endif
@@ -119,6 +119,7 @@ class KeychainController: KeychainControllerProtocol {
fatalError("Something has gone mega wrong, all bets are off.")
}
let restorationToken = RestorationToken(session: session,
sessionDirectory: oldToken.sessionDirectory,
passphrase: oldToken.passphrase,
pusherNotificationClientIdentifier: oldToken.pusherNotificationClientIdentifier)
setRestorationToken(restorationToken, forUsername: session.userId)
@@ -21,6 +21,7 @@ import MatrixRustSDK
final class QRCodeLoginService: QRCodeLoginServiceProtocol {
private let oidcConfiguration: OidcConfiguration
private let sessionDirectory: URL
private let passphrase: String
private let userSessionStore: UserSessionStoreProtocol
@@ -34,6 +35,7 @@ final class QRCodeLoginService: QRCodeLoginServiceProtocol {
userSessionStore: UserSessionStoreProtocol) {
self.oidcConfiguration = oidcConfiguration
self.userSessionStore = userSessionStore
sessionDirectory = .sessionsBaseDirectory.appending(component: UUID().uuidString)
passphrase = encryptionKeyProvider.generateKey().base64EncodedString()
}
@@ -52,7 +54,7 @@ final class QRCodeLoginService: QRCodeLoginServiceProtocol {
do {
let client = try await ClientBuilder()
.basePath(path: userSessionStore.baseDirectory.path(percentEncoded: false))
.sessionPath(path: sessionDirectory.path(percentEncoded: false))
.passphrase(passphrase: passphrase)
.userAgent(userAgent: UserAgentBuilder.makeASCIIUserAgent())
.enableCrossProcessRefreshLock(processId: InfoPlistReader.main.bundleIdentifier,
@@ -70,7 +72,7 @@ final class QRCodeLoginService: QRCodeLoginServiceProtocol {
}
private func login(client: Client) async -> Result<UserSessionProtocol, QRCodeLoginServiceError> {
switch await userSessionStore.userSession(for: client, passphrase: passphrase) {
switch await userSessionStore.userSession(for: client, sessionDirectory: sessionDirectory, passphrase: passphrase) {
case .success(let session):
return .success(session)
case .failure(let error):
@@ -17,12 +17,27 @@
import Foundation
import MatrixRustSDK
struct RestorationToken: Codable, Equatable {
struct RestorationToken: Equatable {
let session: MatrixRustSDK.Session
let sessionDirectory: URL
let passphrase: String?
let pusherNotificationClientIdentifier: String?
}
extension RestorationToken: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let session = try container.decode(MatrixRustSDK.Session.self, forKey: .session)
let sessionDirectory = try container.decodeIfPresent(URL.self, forKey: .sessionDirectory)
self = try .init(session: session,
sessionDirectory: sessionDirectory ?? .legacySessionDirectory(for: session.userId),
passphrase: container.decodeIfPresent(String.self, forKey: .passphrase),
pusherNotificationClientIdentifier: container.decodeIfPresent(String.self, forKey: .pusherNotificationClientIdentifier))
}
}
extension MatrixRustSDK.Session: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
@@ -50,3 +65,18 @@ extension MatrixRustSDK.Session: Codable {
case accessToken, refreshToken, userId, deviceId, homeserverUrl, oidcData, slidingSyncProxy
}
}
// MARK: Migrations
private extension URL {
/// Gets the store directory of a legacy session that hasn't been migrated to the new token format.
///
/// This should only be used to fill in the missing value when restoring a token as older versions of
/// the SDK set the session directory for us, based on the user's ID. Newer sessions now use a UUID,
/// which is generated app side during authentication.
static func legacySessionDirectory(for userID: String) -> URL {
// Rust sanitises the user ID replacing invalid characters with an _
let sanitisedUserID = userID.replacingOccurrences(of: ":", with: "_")
return .sessionsBaseDirectory.appendingPathComponent(sanitisedUserID)
}
}
@@ -27,21 +27,16 @@ class UserSessionStore: UserSessionStoreProtocol {
/// All the user IDs managed by the store.
var userIDs: [String] { keychainController.restorationTokens().map(\.userID) }
/// The base directory where all session data is stored.
let baseDirectory: URL
var clientSessionDelegate: ClientSessionDelegate { keychainController }
init(keychainController: KeychainControllerProtocol) {
self.keychainController = keychainController
baseDirectory = .sessionsBaseDirectory
MXLog.info("Setup base directory at: \(baseDirectory)")
}
/// Deletes all data stored in the shared container and keychain
func reset() {
MXLog.warning("Resetting the UserSessionStore. All accounts will be affected.")
try? FileManager.default.removeItem(at: baseDirectory)
try? FileManager.default.removeItem(at: .sessionsBaseDirectory)
keychainController.removeAllRestorationTokens()
}
@@ -59,20 +54,21 @@ class UserSessionStore: UserSessionStoreProtocol {
MXLog.error("Failed restoring login with error: \(error)")
// On any restoration failure reset the token and restart
keychainController.removeAllRestorationTokens()
deleteSessionDirectory(for: credentials.userID)
keychainController.removeRestorationTokenForUsername(credentials.userID)
deleteSessionDirectory(for: credentials)
return .failure(error)
}
}
func userSession(for client: Client, passphrase: String?) async -> Result<UserSessionProtocol, UserSessionStoreError> {
func userSession(for client: Client, sessionDirectory: URL, passphrase: String?) async -> Result<UserSessionProtocol, UserSessionStoreError> {
do {
let session = try client.session()
let userID = try client.userId()
let clientProxy = await setupProxyForClient(client)
keychainController.setRestorationToken(RestorationToken(session: session,
sessionDirectory: sessionDirectory,
passphrase: passphrase,
pusherNotificationClientIdentifier: clientProxy.pusherNotificationClientIdentifier),
forUsername: userID)
@@ -86,12 +82,20 @@ class UserSessionStore: UserSessionStoreProtocol {
func logout(userSession: UserSessionProtocol) {
let userID = userSession.clientProxy.userID
let credentials = keychainController.restorationTokens().first { $0.userID == userID }
keychainController.removeRestorationTokenForUsername(userID)
deleteSessionDirectory(for: userID)
if let credentials {
deleteSessionDirectory(for: credentials)
}
}
func clearCache(for userID: String) {
deleteCaches(for: userID)
guard let credentials = keychainController.restorationTokens().first(where: { $0.userID == userID }) else {
MXLog.error("Failed to clearing caches: Credentials missing")
return
}
deleteCaches(for: credentials)
}
// MARK: - Private
@@ -115,7 +119,7 @@ class UserSessionStore: UserSessionStoreProtocol {
let homeserverURL = credentials.restorationToken.session.homeserverUrl
var builder = ClientBuilder()
.basePath(path: baseDirectory.path)
.sessionPath(path: credentials.restorationToken.sessionDirectory.path(percentEncoded: false))
.username(username: credentials.userID)
.homeserverUrl(url: homeserverURL)
.passphrase(passphrase: credentials.restorationToken.passphrase)
@@ -148,28 +152,22 @@ class UserSessionStore: UserSessionStoreProtocol {
networkMonitor: ServiceLocator.shared.networkMonitor)
}
private func deleteSessionDirectory(for userID: String) {
private func deleteSessionDirectory(for credentials: KeychainCredentials) {
do {
try FileManager.default.removeItem(at: basePath(for: userID))
try FileManager.default.removeItem(at: credentials.restorationToken.sessionDirectory)
} catch {
MXLog.failure("Failed deleting the session data: \(error)")
}
}
private func deleteCaches(for userID: String) {
private func deleteCaches(for credentials: KeychainCredentials) {
do {
for url in try FileManager.default.contentsOfDirectory(at: basePath(for: userID), includingPropertiesForKeys: nil) where url.path.contains(matrixSDKStateKey) {
let sessionDirectoryContents = try FileManager.default.contentsOfDirectory(at: credentials.restorationToken.sessionDirectory, includingPropertiesForKeys: nil)
for url in sessionDirectoryContents where url.path.contains(matrixSDKStateKey) {
try FileManager.default.removeItem(at: url)
}
} catch {
MXLog.failure("Failed deleting the session data: \(error)")
MXLog.failure("Failed clearing caches: \(error)")
}
}
#warning("We should move this and the caches cleanup to the rust side")
private func basePath(for userID: String) -> URL {
// Rust sanitises the user ID replacing invalid characters with an _
let sanitisedUserID = userID.replacingOccurrences(of: ":", with: "_")
return baseDirectory.appendingPathComponent(sanitisedUserID)
}
}
@@ -33,9 +33,6 @@ protocol UserSessionStoreProtocol {
/// All the user IDs managed by the store.
var userIDs: [String] { get }
/// Returns the location to store user data for a particular username.
var baseDirectory: URL { get }
/// Returns the delegate that should handle any changes to a `Client`'s `Session`.
var clientSessionDelegate: ClientSessionDelegate { get }
@@ -43,7 +40,7 @@ protocol UserSessionStoreProtocol {
func restoreUserSession() async -> Result<UserSessionProtocol, UserSessionStoreError>
/// Creates a user session for a new client from the SDK along with the passphrase used for the data stores.
func userSession(for client: Client, passphrase: String?) async -> Result<UserSessionProtocol, UserSessionStoreError>
func userSession(for client: Client, sessionDirectory: URL, passphrase: String?) async -> Result<UserSessionProtocol, UserSessionStoreError>
/// Logs out of the specified session.
func logout(userSession: UserSessionProtocol)