From 0657cddf92a884c2faeb08be7328b6859e90b3f2 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Sun, 7 Apr 2024 02:25:21 +0200 Subject: [PATCH 1/4] Fix a crash when error is nil for watch streams --- CHANGELOG.md | 2 +- Realm/ObjectServerTests/RealmServer.swift | 8 ++++++ .../SwiftObjectServerTests.swift | 26 +++++++++++++++++++ .../ObjectServerTests/WatchTestUtility.swift | 15 ++++++++--- Realm/RLMNetworkTransport.mm | 6 +++-- 5 files changed, 51 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7f5b4c515..08df55ccac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ x.y.z Release notes (yyyy-MM-dd) ### Fixed * ([#????](https://github.com/realm/realm-swift/issues/????), since v?.?.?) -* None. +* Fixed a crash that would occur when an http error 401 or 403 is returned upon opening a watch stream for a MongoDB collection. ([#8519](https://github.com/realm/realm-swift/issues/8519)) diff --git a/Realm/ObjectServerTests/RealmServer.swift b/Realm/ObjectServerTests/RealmServer.swift index e5c1264e93..2af44a8a29 100644 --- a/Realm/ObjectServerTests/RealmServer.swift +++ b/Realm/ObjectServerTests/RealmServer.swift @@ -1187,6 +1187,14 @@ public class RealmServer: NSObject { return session.apps[appServerId].users[userId].delete() } + public func revokeUserSessions(_ appId: String, userId: String) -> Result { + guard let appServerId = try? RealmServer.shared.retrieveAppServerId(appId), + let session = session else { + return .failure(URLError(.unknown)) + } + return session.apps[appServerId].users[userId].logout.put([:]); + } + public func retrieveSchemaProperties(_ appId: String, className: String, _ completion: @escaping (Result<[String], Error>) -> Void) { guard let appServerId = try? RealmServer.shared.retrieveAppServerId(appId), let session = session else { diff --git a/Realm/ObjectServerTests/SwiftObjectServerTests.swift b/Realm/ObjectServerTests/SwiftObjectServerTests.swift index cdd0b5ab92..b922703d2e 100644 --- a/Realm/ObjectServerTests/SwiftObjectServerTests.swift +++ b/Realm/ObjectServerTests/SwiftObjectServerTests.swift @@ -1322,6 +1322,32 @@ class SwiftObjectServerTests: SwiftSyncTestCase { let objectCol = (obj!.anyCol.dynamicObject?.objectCol as? Object) XCTAssertEqual((objectCol?["firstName"] as? String), "Morty") } + + func testRevokeUserSessions() { + let email = "realm_tests_do_autoverify\(randomString(7))@\(randomString(7)).com" + let password = randomString(10) + + app.emailPasswordAuth.registerUser(email: email, password: password).await(self) + + let syncUser = app.login(credentials: Credentials.emailPassword(email: email, password: password)).await(self) + + // Should succeed refreshing custom data + syncUser.refreshCustomData().await(self) + + _ = try? RealmServer.shared.revokeUserSessions(appId, userId: syncUser.id).get() + + // Should fail refreshing custom data. This verifies we're correctly handling the error in RLMSessionDelegate + syncUser.refreshCustomData().awaitFailure(self) + + // This verifies that we don't crash in RLMEventSessionDelegate when creating a watch stream + // with a revoked user. See https://github.com/realm/realm-swift/issues/8519 + let watchTestUtility = WatchTestUtility(testCase: self, expectError: true) + _ = syncUser.collection(for: Dog.self, app: app).watch(delegate: watchTestUtility) + watchTestUtility.waitForOpen() + watchTestUtility.waitForClose() + + XCTAssertEqual(watchTestUtility.closeError?.localizedDescription, "URLSession HTTP error code: 403") + } } #endif // os(macOS) diff --git a/Realm/ObjectServerTests/WatchTestUtility.swift b/Realm/ObjectServerTests/WatchTestUtility.swift index c707e9e614..e8a416a4f3 100644 --- a/Realm/ObjectServerTests/WatchTestUtility.swift +++ b/Realm/ObjectServerTests/WatchTestUtility.swift @@ -23,13 +23,16 @@ import XCTest final public class WatchTestUtility: ChangeEventDelegate { private let testCase: XCTestCase private let matchingObjectId: ObjectId? - private let openExpectation: XCTestExpectation + private var openExpectation: XCTestExpectation private let closeExpectation: XCTestExpectation private var changeExpectation: XCTestExpectation? + private let expectError: Bool + public var closeError: Error? - public init(testCase: XCTestCase, matchingObjectId: ObjectId? = nil) { + public init(testCase: XCTestCase, matchingObjectId: ObjectId? = nil, expectError: Bool = false) { self.testCase = testCase self.matchingObjectId = matchingObjectId + self.expectError = expectError openExpectation = testCase.expectation(description: "Open watch stream") closeExpectation = testCase.expectation(description: "Close watch stream") } @@ -57,7 +60,13 @@ final public class WatchTestUtility: ChangeEventDelegate { } public func changeStreamDidClose(with error: Error?) { - XCTAssertNil(error) + if (expectError) { + XCTAssertNotNil(error) + } else { + XCTAssertNil(error) + } + + closeError = error closeExpectation.fulfill() } diff --git a/Realm/RLMNetworkTransport.mm b/Realm/RLMNetworkTransport.mm index edd12cf56e..d1af28e9d0 100644 --- a/Realm/RLMNetworkTransport.mm +++ b/Realm/RLMNetworkTransport.mm @@ -226,12 +226,14 @@ - (void)URLSession:(__unused NSURLSession *)session NSString *errorStatus = [NSString stringWithFormat:@"URLSession HTTP error code: %ld", (long)httpResponse.statusCode]; + + // error may be nil when the http status code is 401/403 - replace it with [NSNull null] in that case NSError *wrappedError = [NSError errorWithDomain:RLMAppErrorDomain code:RLMAppErrorHttpRequestFailed userInfo:@{NSLocalizedDescriptionKey: errorStatus, - RLMHTTPStatusCodeKey: @(httpResponse.statusCode), + RLMHTTPStatusCodeKey: @(httpResponse.statusCode), NSURLErrorFailingURLErrorKey: task.currentRequest.URL, - NSUnderlyingErrorKey: error}]; + NSUnderlyingErrorKey: error?: [NSNull null]}]; return [_subscriber didCloseWithError:wrappedError]; } From 195039ee72964d72b7eea2c48dfe9446df6bc666 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Sun, 7 Apr 2024 02:30:51 +0200 Subject: [PATCH 2/4] var -> let --- Realm/ObjectServerTests/WatchTestUtility.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Realm/ObjectServerTests/WatchTestUtility.swift b/Realm/ObjectServerTests/WatchTestUtility.swift index e8a416a4f3..c5ebfa9072 100644 --- a/Realm/ObjectServerTests/WatchTestUtility.swift +++ b/Realm/ObjectServerTests/WatchTestUtility.swift @@ -23,7 +23,7 @@ import XCTest final public class WatchTestUtility: ChangeEventDelegate { private let testCase: XCTestCase private let matchingObjectId: ObjectId? - private var openExpectation: XCTestExpectation + private let openExpectation: XCTestExpectation private let closeExpectation: XCTestExpectation private var changeExpectation: XCTestExpectation? private let expectError: Bool From 1e7878f426c6fd46ff14b1bc3863162caa84ad30 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Mon, 8 Apr 2024 23:14:38 +0200 Subject: [PATCH 3/4] Assert underlying error is nil --- CHANGELOG.md | 3 ++- Realm/ObjectServerTests/SwiftObjectServerTests.swift | 5 ++++- Realm/ObjectServerTests/WatchTestUtility.swift | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08df55ccac..a030866bec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ x.y.z Release notes (yyyy-MM-dd) ### Fixed * ([#????](https://github.com/realm/realm-swift/issues/????), since v?.?.?) -* Fixed a crash that would occur when an http error 401 or 403 is returned upon opening a watch stream for a MongoDB collection. ([#8519](https://github.com/realm/realm-swift/issues/8519)) +* Fixed a crash that would occur when an http error 401 or 403 is returned upon + opening a watch stream for a MongoDB collection. ([#8519](https://github.com/realm/realm-swift/issues/8519)) diff --git a/Realm/ObjectServerTests/SwiftObjectServerTests.swift b/Realm/ObjectServerTests/SwiftObjectServerTests.swift index b922703d2e..b4fd2b6b68 100644 --- a/Realm/ObjectServerTests/SwiftObjectServerTests.swift +++ b/Realm/ObjectServerTests/SwiftObjectServerTests.swift @@ -1346,7 +1346,10 @@ class SwiftObjectServerTests: SwiftSyncTestCase { watchTestUtility.waitForOpen() watchTestUtility.waitForClose() - XCTAssertEqual(watchTestUtility.closeError?.localizedDescription, "URLSession HTTP error code: 403") + let didCloseError = watchTestUtility.didCloseError! as NSError + XCTAssertNotNil(didCloseError) + XCTAssertEqual(didCloseError.localizedDescription, "URLSession HTTP error code: 403") + XCTAssertNil(didCloseError.userInfo[NSUnderlyingErrorKey]) } } diff --git a/Realm/ObjectServerTests/WatchTestUtility.swift b/Realm/ObjectServerTests/WatchTestUtility.swift index c5ebfa9072..fa2ef36c52 100644 --- a/Realm/ObjectServerTests/WatchTestUtility.swift +++ b/Realm/ObjectServerTests/WatchTestUtility.swift @@ -27,7 +27,7 @@ final public class WatchTestUtility: ChangeEventDelegate { private let closeExpectation: XCTestExpectation private var changeExpectation: XCTestExpectation? private let expectError: Bool - public var closeError: Error? + public var didCloseError: Error? public init(testCase: XCTestCase, matchingObjectId: ObjectId? = nil, expectError: Bool = false) { self.testCase = testCase @@ -66,7 +66,7 @@ final public class WatchTestUtility: ChangeEventDelegate { XCTAssertNil(error) } - closeError = error + didCloseError = error closeExpectation.fulfill() } From 20b7fa5a5eea61bd806e2dd7386a559d03fcb88f Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Mon, 8 Apr 2024 23:38:27 +0200 Subject: [PATCH 4/4] Fix lint violations --- Realm/ObjectServerTests/RealmServer.swift | 2 +- Realm/ObjectServerTests/SwiftObjectServerTests.swift | 2 +- Realm/ObjectServerTests/WatchTestUtility.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Realm/ObjectServerTests/RealmServer.swift b/Realm/ObjectServerTests/RealmServer.swift index 2af44a8a29..ea68afc070 100644 --- a/Realm/ObjectServerTests/RealmServer.swift +++ b/Realm/ObjectServerTests/RealmServer.swift @@ -1192,7 +1192,7 @@ public class RealmServer: NSObject { let session = session else { return .failure(URLError(.unknown)) } - return session.apps[appServerId].users[userId].logout.put([:]); + return session.apps[appServerId].users[userId].logout.put([:]) } public func retrieveSchemaProperties(_ appId: String, className: String, _ completion: @escaping (Result<[String], Error>) -> Void) { diff --git a/Realm/ObjectServerTests/SwiftObjectServerTests.swift b/Realm/ObjectServerTests/SwiftObjectServerTests.swift index b4fd2b6b68..fc5b7b5477 100644 --- a/Realm/ObjectServerTests/SwiftObjectServerTests.swift +++ b/Realm/ObjectServerTests/SwiftObjectServerTests.swift @@ -1330,7 +1330,7 @@ class SwiftObjectServerTests: SwiftSyncTestCase { app.emailPasswordAuth.registerUser(email: email, password: password).await(self) let syncUser = app.login(credentials: Credentials.emailPassword(email: email, password: password)).await(self) - + // Should succeed refreshing custom data syncUser.refreshCustomData().await(self) diff --git a/Realm/ObjectServerTests/WatchTestUtility.swift b/Realm/ObjectServerTests/WatchTestUtility.swift index fa2ef36c52..3bca8465cc 100644 --- a/Realm/ObjectServerTests/WatchTestUtility.swift +++ b/Realm/ObjectServerTests/WatchTestUtility.swift @@ -60,7 +60,7 @@ final public class WatchTestUtility: ChangeEventDelegate { } public func changeStreamDidClose(with error: Error?) { - if (expectError) { + if expectError { XCTAssertNotNil(error) } else { XCTAssertNil(error)