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 059061b995
commit 3f57668f56
17 changed files with 150 additions and 113 deletions

View File

@@ -7335,7 +7335,7 @@
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
requirement = {
kind = exactVersion;
version = 1.0.9;
version = 1.0.10;
};
};
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {

View File

@@ -139,8 +139,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
"state" : {
"revision" : "4f46a00c5a0ab3053f49f449b769237645f00b18",
"version" : "1.0.9"
"revision" : "0f9794c5cba9e0ad2084f0c3547eb1c45d0eefeb",
"version" : "1.0.10"
}
},
{

View File

@@ -161,7 +161,8 @@ final class AppSettings {
tosURI: acceptableUseURL,
policyURI: privacyURL,
contacts: [supportEmailAddress],
staticRegistrations: oidcStaticRegistrations.mapKeys { $0.absoluteString })
staticRegistrations: oidcStaticRegistrations.mapKeys { $0.absoluteString },
dynamicRegistrationsFile: .sessionsBaseDirectory.appending(path: "oidc/registrations.json"))
/// A dictionary of accounts that have performed an initial sync through their proxy.
///

View File

@@ -3688,75 +3688,6 @@ open class ClientBuilderSDKMock: MatrixRustSDK.ClientBuilder {
}
}
//MARK: - basePath
var basePathPathUnderlyingCallsCount = 0
open var basePathPathCallsCount: Int {
get {
if Thread.isMainThread {
return basePathPathUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = basePathPathUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
basePathPathUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
basePathPathUnderlyingCallsCount = newValue
}
}
}
}
open var basePathPathCalled: Bool {
return basePathPathCallsCount > 0
}
open var basePathPathReceivedPath: String?
open var basePathPathReceivedInvocations: [String] = []
var basePathPathUnderlyingReturnValue: ClientBuilder!
open var basePathPathReturnValue: ClientBuilder! {
get {
if Thread.isMainThread {
return basePathPathUnderlyingReturnValue
} else {
var returnValue: ClientBuilder? = nil
DispatchQueue.main.sync {
returnValue = basePathPathUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
basePathPathUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
basePathPathUnderlyingReturnValue = newValue
}
}
}
}
open var basePathPathClosure: ((String) -> ClientBuilder)?
open override func basePath(path: String) -> ClientBuilder {
basePathPathCallsCount += 1
basePathPathReceivedPath = path
basePathPathReceivedInvocations.append(path)
if let basePathPathClosure = basePathPathClosure {
return basePathPathClosure(path)
} else {
return basePathPathReturnValue
}
}
//MARK: - build
open var buildThrowableError: Error?
@@ -4512,6 +4443,75 @@ open class ClientBuilderSDKMock: MatrixRustSDK.ClientBuilder {
}
}
//MARK: - sessionPath
var sessionPathPathUnderlyingCallsCount = 0
open var sessionPathPathCallsCount: Int {
get {
if Thread.isMainThread {
return sessionPathPathUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = sessionPathPathUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
sessionPathPathUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
sessionPathPathUnderlyingCallsCount = newValue
}
}
}
}
open var sessionPathPathCalled: Bool {
return sessionPathPathCallsCount > 0
}
open var sessionPathPathReceivedPath: String?
open var sessionPathPathReceivedInvocations: [String] = []
var sessionPathPathUnderlyingReturnValue: ClientBuilder!
open var sessionPathPathReturnValue: ClientBuilder! {
get {
if Thread.isMainThread {
return sessionPathPathUnderlyingReturnValue
} else {
var returnValue: ClientBuilder? = nil
DispatchQueue.main.sync {
returnValue = sessionPathPathUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
sessionPathPathUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
sessionPathPathUnderlyingReturnValue = newValue
}
}
}
}
open var sessionPathPathClosure: ((String) -> ClientBuilder)?
open override func sessionPath(path: String) -> ClientBuilder {
sessionPathPathCallsCount += 1
sessionPathPathReceivedPath = path
sessionPathPathReceivedInvocations.append(path)
if let sessionPathPathClosure = sessionPathPathClosure {
return sessionPathPathClosure(path)
} else {
return sessionPathPathReturnValue
}
}
//MARK: - setSessionDelegate
var setSessionDelegateSessionDelegateUnderlyingCallsCount = 0

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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):

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -33,7 +33,7 @@ final class NSEUserSession {
let homeserverURL = credentials.restorationToken.session.homeserverUrl
var clientBuilder = ClientBuilder()
.basePath(path: URL.sessionsBaseDirectory.path)
.sessionPath(path: credentials.restorationToken.sessionDirectory.path(percentEncoded: false))
.username(username: credentials.userID)
.homeserverUrl(url: homeserverURL)
.passphrase(passphrase: credentials.restorationToken.passphrase)

View File

@@ -14,7 +14,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/element-hq/swift-command-line-tools.git",
"state" : {
"revision" : "a6ad90808f4f6cac615ab8496c6ff1bc5f9fa192"
"revision" : "e5eaab1558ef664e6cd80493f64259381670fb3a"
}
},
{

View File

@@ -13,7 +13,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", .upToNextMinor(from: "1.4.0")),
.package(url: "https://github.com/element-hq/swift-command-line-tools.git", revision: "a6ad90808f4f6cac615ab8496c6ff1bc5f9fa192"),
.package(url: "https://github.com/element-hq/swift-command-line-tools.git", revision: "e5eaab1558ef664e6cd80493f64259381670fb3a"),
// .package(path: "../../../swift-command-line-tools"),
.package(url: "https://github.com/jpsim/Yams", .upToNextMinor(from: "5.1.2"))
],

View File

@@ -12,7 +12,7 @@ struct GenerateSDKMocks: ParsableCommand {
@Argument(help: "The argument to specify a branch of the SDK. Use `local` to use your local version")
var version: String
private var fileURLFormat = "https://raw.githubusercontent.com/matrix-org/matrix-rust-components-swift/%@/Sources/MatrixRustSDK/matrix_sdk_ffi.swift"
private var fileURLFormat = "https://raw.githubusercontent.com/element-hq/matrix-rust-components-swift/%@/Sources/MatrixRustSDK/matrix_sdk_ffi.swift"
func run() throws {
if version == "local" {

View File

@@ -40,6 +40,7 @@ class KeychainControllerTests: XCTestCase {
homeserverUrl: "homeserverUrl",
oidcData: "oidcData",
slidingSyncProxy: "https://my.sync.proxy"),
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
passphrase: "passphrase",
pusherNotificationClientIdentifier: "pusherClientID")
keychain.setRestorationToken(restorationToken, forUsername: username)
@@ -58,6 +59,7 @@ class KeychainControllerTests: XCTestCase {
homeserverUrl: "homeserverUrl",
oidcData: "oidcData",
slidingSyncProxy: "https://my.sync.proxy"),
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
passphrase: "passphrase",
pusherNotificationClientIdentifier: "pusherClientID")
keychain.setRestorationToken(restorationToken, forUsername: username)
@@ -82,6 +84,7 @@ class KeychainControllerTests: XCTestCase {
homeserverUrl: "homeserverUrl",
oidcData: "oidcData",
slidingSyncProxy: "https://my.sync.proxy"),
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
passphrase: "passphrase",
pusherNotificationClientIdentifier: "pusherClientID")
keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com")
@@ -105,6 +108,7 @@ class KeychainControllerTests: XCTestCase {
homeserverUrl: "homeserverUrl",
oidcData: "oidcData",
slidingSyncProxy: "https://my.sync.proxy"),
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
passphrase: "passphrase",
pusherNotificationClientIdentifier: "pusherClientID")
keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com")

View File

@@ -49,7 +49,7 @@ packages:
# Element/Matrix dependencies
MatrixRustSDK:
url: https://github.com/element-hq/matrix-rust-components-swift
exactVersion: 1.0.9
exactVersion: 1.0.10
# path: ../matrix-rust-sdk
Compound:
url: https://github.com/element-hq/compound-ios