mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Adopt Rust's new sessionPath that replaces the basePath (#2903)
This commit is contained in:
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user