mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
Drop support for the legacy session directory and optional store encryption.
Store encryption became mandatory in #2692 Rust's sessionPath replaced the legacy session directory in #2903 Native sliding sync migration resulted in new restoration tokens for new users in #3071 and later for all users by #3801.
This commit is contained in:
@@ -18943,8 +18943,8 @@ class UserSessionStoreMock: UserSessionStoreProtocol, @unchecked Sendable {
|
|||||||
var userSessionForSessionDirectoriesPassphraseCalled: Bool {
|
var userSessionForSessionDirectoriesPassphraseCalled: Bool {
|
||||||
return userSessionForSessionDirectoriesPassphraseCallsCount > 0
|
return userSessionForSessionDirectoriesPassphraseCallsCount > 0
|
||||||
}
|
}
|
||||||
var userSessionForSessionDirectoriesPassphraseReceivedArguments: (client: ClientProtocol, sessionDirectories: SessionDirectories, passphrase: String?)?
|
var userSessionForSessionDirectoriesPassphraseReceivedArguments: (client: ClientProtocol, sessionDirectories: SessionDirectories, passphrase: String)?
|
||||||
var userSessionForSessionDirectoriesPassphraseReceivedInvocations: [(client: ClientProtocol, sessionDirectories: SessionDirectories, passphrase: String?)] = []
|
var userSessionForSessionDirectoriesPassphraseReceivedInvocations: [(client: ClientProtocol, sessionDirectories: SessionDirectories, passphrase: String)] = []
|
||||||
|
|
||||||
var userSessionForSessionDirectoriesPassphraseUnderlyingReturnValue: Result<UserSessionProtocol, UserSessionStoreError>!
|
var userSessionForSessionDirectoriesPassphraseUnderlyingReturnValue: Result<UserSessionProtocol, UserSessionStoreError>!
|
||||||
var userSessionForSessionDirectoriesPassphraseReturnValue: Result<UserSessionProtocol, UserSessionStoreError>! {
|
var userSessionForSessionDirectoriesPassphraseReturnValue: Result<UserSessionProtocol, UserSessionStoreError>! {
|
||||||
@@ -18970,9 +18970,9 @@ class UserSessionStoreMock: UserSessionStoreProtocol, @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var userSessionForSessionDirectoriesPassphraseClosure: ((ClientProtocol, SessionDirectories, String?) async -> Result<UserSessionProtocol, UserSessionStoreError>)?
|
var userSessionForSessionDirectoriesPassphraseClosure: ((ClientProtocol, SessionDirectories, String) async -> Result<UserSessionProtocol, UserSessionStoreError>)?
|
||||||
|
|
||||||
func userSession(for client: ClientProtocol, sessionDirectories: SessionDirectories, passphrase: String?) async -> Result<UserSessionProtocol, UserSessionStoreError> {
|
func userSession(for client: ClientProtocol, sessionDirectories: SessionDirectories, passphrase: String) async -> Result<UserSessionProtocol, UserSessionStoreError> {
|
||||||
userSessionForSessionDirectoriesPassphraseCallsCount += 1
|
userSessionForSessionDirectoriesPassphraseCallsCount += 1
|
||||||
userSessionForSessionDirectoriesPassphraseReceivedArguments = (client: client, sessionDirectories: sessionDirectories, passphrase: passphrase)
|
userSessionForSessionDirectoriesPassphraseReceivedArguments = (client: client, sessionDirectories: sessionDirectories, passphrase: passphrase)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ enum RestorationTokenError: Error {
|
|||||||
struct RestorationToken: Equatable {
|
struct RestorationToken: Equatable {
|
||||||
let session: MatrixRustSDK.Session
|
let session: MatrixRustSDK.Session
|
||||||
let sessionDirectories: SessionDirectories
|
let sessionDirectories: SessionDirectories
|
||||||
let passphrase: String?
|
let passphrase: String
|
||||||
let pusherNotificationClientIdentifier: String?
|
let pusherNotificationClientIdentifier: String?
|
||||||
|
|
||||||
enum CodingKeys: CodingKey {
|
enum CodingKeys: CodingKey {
|
||||||
@@ -32,22 +32,18 @@ extension RestorationToken: Codable {
|
|||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
let session = try container.decode(Session.self, forKey: .session)
|
let session = try container.decode(Session.self, forKey: .session)
|
||||||
let dataDirectory = try container.decodeIfPresent(URL.self, forKey: .sessionDirectory)
|
let dataDirectory = try container.decode(URL.self, forKey: .sessionDirectory)
|
||||||
let cacheDirectory = try container.decodeIfPresent(URL.self, forKey: .cacheDirectory)
|
let cacheDirectory = try container.decodeIfPresent(URL.self, forKey: .cacheDirectory)
|
||||||
|
|
||||||
let sessionDirectories = if let dataDirectory {
|
let sessionDirectories = if let cacheDirectory {
|
||||||
if let cacheDirectory {
|
SessionDirectories(dataDirectory: dataDirectory, cacheDirectory: cacheDirectory)
|
||||||
SessionDirectories(dataDirectory: dataDirectory, cacheDirectory: cacheDirectory)
|
|
||||||
} else {
|
|
||||||
SessionDirectories(dataDirectory: dataDirectory)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
SessionDirectories(userID: session.userId)
|
SessionDirectories(dataDirectory: dataDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
self = try .init(session: session,
|
self = try .init(session: session,
|
||||||
sessionDirectories: sessionDirectories,
|
sessionDirectories: sessionDirectories,
|
||||||
passphrase: container.decodeIfPresent(String.self, forKey: .passphrase),
|
passphrase: container.decode(String.self, forKey: .passphrase),
|
||||||
pusherNotificationClientIdentifier: container.decodeIfPresent(String.self, forKey: .pusherNotificationClientIdentifier))
|
pusherNotificationClientIdentifier: container.decodeIfPresent(String.self, forKey: .pusherNotificationClientIdentifier))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,12 +75,6 @@ extension SessionDirectories {
|
|||||||
cacheDirectory = .sessionCachesBaseDirectory.appending(component: sessionDirectoryName)
|
cacheDirectory = .sessionCachesBaseDirectory.appending(component: sessionDirectoryName)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates the session directories for a user who signed in before the data directory was stored.
|
|
||||||
init(userID: String) {
|
|
||||||
dataDirectory = .legacySessionDirectory(for: userID)
|
|
||||||
cacheDirectory = .sessionCachesBaseDirectory.appending(component: dataDirectory.lastPathComponent)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates the session directories for a user who has a single session directory stored without a separate caches directory.
|
/// Creates the session directories for a user who has a single session directory stored without a separate caches directory.
|
||||||
init(dataDirectory: URL) {
|
init(dataDirectory: URL) {
|
||||||
self.dataDirectory = dataDirectory
|
self.dataDirectory = dataDirectory
|
||||||
@@ -93,18 +87,3 @@ extension SessionDirectories: CustomStringConvertible {
|
|||||||
"Data: \(dataPath) Caches: \(cachePath)"
|
"Data: \(dataPath) Caches: \(cachePath)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class UserSessionStore: UserSessionStoreProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func userSession(for client: ClientProtocol, sessionDirectories: SessionDirectories, passphrase: String?) async -> Result<UserSessionProtocol, UserSessionStoreError> {
|
func userSession(for client: ClientProtocol, sessionDirectories: SessionDirectories, passphrase: String) async -> Result<UserSessionProtocol, UserSessionStoreError> {
|
||||||
do {
|
do {
|
||||||
let session = try client.session()
|
let session = try client.session()
|
||||||
let userID = try client.userId()
|
let userID = try client.userId()
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ protocol UserSessionStoreProtocol {
|
|||||||
func restoreUserSession() async -> Result<UserSessionProtocol, UserSessionStoreError>
|
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.
|
/// Creates a user session for a new client from the SDK along with the passphrase used for the data stores.
|
||||||
func userSession(for client: ClientProtocol, sessionDirectories: SessionDirectories, passphrase: String?) async -> Result<UserSessionProtocol, UserSessionStoreError>
|
func userSession(for client: ClientProtocol, sessionDirectories: SessionDirectories, passphrase: String) async -> Result<UserSessionProtocol, UserSessionStoreError>
|
||||||
|
|
||||||
/// Logs out of the specified session.
|
/// Logs out of the specified session.
|
||||||
func logout(userSession: UserSessionProtocol)
|
func logout(userSession: UserSessionProtocol)
|
||||||
|
|||||||
@@ -127,13 +127,16 @@ class KeychainControllerTests: XCTestCase {
|
|||||||
XCTAssertTrue(underlyingKeychain.allKeys().isEmpty, "The keychain should be empty to begin with.")
|
XCTAssertTrue(underlyingKeychain.allKeys().isEmpty, "The keychain should be empty to begin with.")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let unsupportedToken = RestorationTokenV1(session: SessionV1(accessToken: "1234",
|
let unsupportedToken = RestorationTokenV4(session: SessionV1(accessToken: "1234",
|
||||||
refreshToken: nil,
|
refreshToken: nil,
|
||||||
userId: "@test:example.com",
|
userId: "@test:example.com",
|
||||||
deviceId: "D3V1C3",
|
deviceId: "D3V1C3",
|
||||||
homeserverUrl: "https://matrix.example.com",
|
homeserverUrl: "https://matrix.example.com",
|
||||||
oidcData: nil,
|
oidcData: nil,
|
||||||
slidingSyncVersion: .proxy(url: "https://sync.example.com")))
|
slidingSyncVersion: .proxy(url: "https://sync.example.com")),
|
||||||
|
sessionDirectory: .sessionsBaseDirectory.appending(component: UUID().uuidString),
|
||||||
|
passphrase: "passphrase",
|
||||||
|
pusherNotificationClientIdentifier: "pusherClientID")
|
||||||
let tokenData = try JSONEncoder().encode(unsupportedToken)
|
let tokenData = try JSONEncoder().encode(unsupportedToken)
|
||||||
try underlyingKeychain.set(tokenData, key: "@test:example.com")
|
try underlyingKeychain.set(tokenData, key: "@test:example.com")
|
||||||
XCTAssertEqual(underlyingKeychain.allKeys().count, 1)
|
XCTAssertEqual(underlyingKeychain.allKeys().count, 1)
|
||||||
|
|||||||
@@ -11,15 +11,18 @@ import XCTest
|
|||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
|
|
||||||
class RestorationTokenTests: XCTestCase {
|
class RestorationTokenTests: XCTestCase {
|
||||||
func testDecodeTokenV1WithSlidingSyncProxy() throws {
|
func testDecodeTokenWithSlidingSyncProxy() throws {
|
||||||
// Given an encoded restoration token that contains a session with a sliding sync proxy.
|
// Given an encoded restoration token that contains a session with a sliding sync proxy.
|
||||||
let originalToken = RestorationTokenV1(session: SessionV1(accessToken: "1234",
|
let originalToken = RestorationTokenV4(session: SessionV1(accessToken: "1234",
|
||||||
refreshToken: nil,
|
refreshToken: "5678",
|
||||||
userId: "@user:example.com",
|
userId: "@user:example.com",
|
||||||
deviceId: "D3V1C3",
|
deviceId: "D3V1C3",
|
||||||
homeserverUrl: "https://matrix.example.com",
|
homeserverUrl: "https://matrix.example.com",
|
||||||
oidcData: nil,
|
oidcData: "data-from-mas",
|
||||||
slidingSyncVersion: .proxy(url: "https://sync.example.com")))
|
slidingSyncVersion: .proxy(url: "https://sync.example.com")),
|
||||||
|
sessionDirectory: .sessionsBaseDirectory.appending(component: UUID().uuidString),
|
||||||
|
passphrase: "passphrase",
|
||||||
|
pusherNotificationClientIdentifier: "pusher-identifier")
|
||||||
let data = try JSONEncoder().encode(originalToken)
|
let data = try JSONEncoder().encode(originalToken)
|
||||||
|
|
||||||
// When decoding the data to the current restoration token format.
|
// When decoding the data to the current restoration token format.
|
||||||
@@ -34,32 +37,6 @@ class RestorationTokenTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Honestly not sure if this test is needed or not – it is quite possible that `RestorationTokenV1` was only encoded at a time
|
|
||||||
/// when the sliding sync proxy was the only option. But lets keep the test for completeness of the session directory migration coverage.
|
|
||||||
func testDecodeFromTokenV1() throws {
|
|
||||||
// Given an encoded restoration token in the original format that only contains a Session from the SDK.
|
|
||||||
let originalToken = RestorationTokenV1(session: SessionV1(accessToken: "1234",
|
|
||||||
refreshToken: nil,
|
|
||||||
userId: "@user:example.com",
|
|
||||||
deviceId: "D3V1C3",
|
|
||||||
homeserverUrl: "https://matrix.example.com",
|
|
||||||
oidcData: nil,
|
|
||||||
slidingSyncVersion: .native))
|
|
||||||
let data = try JSONEncoder().encode(originalToken)
|
|
||||||
|
|
||||||
// When decoding the data to the current restoration token format.
|
|
||||||
let decodedToken = try JSONDecoder().decode(RestorationToken.self, from: data)
|
|
||||||
|
|
||||||
// Then the output should be a valid token with the expected store directories.
|
|
||||||
assertEqual(session: decodedToken.session, originalSession: originalToken.session)
|
|
||||||
XCTAssertNil(decodedToken.passphrase, "There should not be a passphrase.")
|
|
||||||
XCTAssertNil(decodedToken.pusherNotificationClientIdentifier, "There should not be a push notification client ID.")
|
|
||||||
XCTAssertEqual(decodedToken.sessionDirectories.dataDirectory, .sessionsBaseDirectory.appending(component: "@user_example.com"),
|
|
||||||
"The session directory should match the original location set by the Rust SDK from our base directory.")
|
|
||||||
XCTAssertEqual(decodedToken.sessionDirectories.cacheDirectory, .sessionCachesBaseDirectory.appending(component: "@user_example.com"),
|
|
||||||
"The cache directory should be derived from the session directory but in the caches directory.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func testDecodeFromTokenV4() throws {
|
func testDecodeFromTokenV4() throws {
|
||||||
// Given an encoded restoration token in the 4th format that contains a stored session directory.
|
// Given an encoded restoration token in the 4th format that contains a stored session directory.
|
||||||
let sessionDirectoryName = UUID().uuidString
|
let sessionDirectoryName = UUID().uuidString
|
||||||
@@ -152,14 +129,10 @@ class RestorationTokenTests: XCTestCase {
|
|||||||
|
|
||||||
// MARK: - Token formats
|
// MARK: - Token formats
|
||||||
|
|
||||||
struct RestorationTokenV1: Equatable, Codable {
|
|
||||||
let session: SessionV1
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RestorationTokenV4: Equatable, Codable {
|
struct RestorationTokenV4: Equatable, Codable {
|
||||||
let session: SessionV1
|
let session: SessionV1
|
||||||
let sessionDirectory: URL
|
let sessionDirectory: URL
|
||||||
let passphrase: String?
|
let passphrase: String? // Optional but has always been encoded for sessions that use native sliding sync.
|
||||||
let pusherNotificationClientIdentifier: String?
|
let pusherNotificationClientIdentifier: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +140,7 @@ struct RestorationTokenV5: Equatable, Codable {
|
|||||||
let session: SessionV1
|
let session: SessionV1
|
||||||
let sessionDirectory: URL
|
let sessionDirectory: URL
|
||||||
let cacheDirectory: URL
|
let cacheDirectory: URL
|
||||||
let passphrase: String?
|
let passphrase: String? // Optional but has always been encoded for sessions that use native sliding sync.
|
||||||
let pusherNotificationClientIdentifier: String?
|
let pusherNotificationClientIdentifier: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,18 +12,6 @@ import XCTest
|
|||||||
class SessionDirectoriesTests: XCTestCase {
|
class SessionDirectoriesTests: XCTestCase {
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
|
|
||||||
func testInitWithUserID() {
|
|
||||||
// Given only a user ID.
|
|
||||||
let userID = "@user:matrix.org"
|
|
||||||
|
|
||||||
// When creating the session directories using this.
|
|
||||||
let sessionDirectories = SessionDirectories(userID: userID)
|
|
||||||
|
|
||||||
// Then the directories should be generated in the correct location, using an escaped version of the user ID
|
|
||||||
XCTAssertEqual(sessionDirectories.dataDirectory, .sessionsBaseDirectory.appending(component: "@user_matrix.org"))
|
|
||||||
XCTAssertEqual(sessionDirectories.cacheDirectory, .sessionCachesBaseDirectory.appending(component: "@user_matrix.org"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testInitWithDataDirectory() {
|
func testInitWithDataDirectory() {
|
||||||
// Given only a session directory without a caches directory.
|
// Given only a session directory without a caches directory.
|
||||||
let sessionDirectoryName = UUID().uuidString
|
let sessionDirectoryName = UUID().uuidString
|
||||||
|
|||||||
Reference in New Issue
Block a user