Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RCOCOA-2310: Fix a crash when receiving 401/403 when opening a watch stream #8536

Merged
merged 4 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ x.y.z Release notes (yyyy-MM-dd)

### Fixed
* <How to hit and notice issue? what was the impact?> ([#????](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))

<!-- ### Breaking Changes - ONLY INCLUDE FOR NEW MAJOR version -->

Expand Down
8 changes: 8 additions & 0 deletions Realm/ObjectServerTests/RealmServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@
}

/// Shared RealmServer. This class only needs to be initialized and torn down once per test suite run.
@objc public static let shared = RealmServer()

Check notice on line 493 in Realm/ObjectServerTests/RealmServer.swift

View check run for this annotation

Xcode Cloud / RealmSwift | swiftui-sync_15.3 | Test - macOS

Realm/ObjectServerTests/RealmServer.swift#L493

Static property 'shared' is not concurrency-safe because it is not either conforming to 'Sendable' or isolated to a global actor; this is an error in Swift 6

Check notice on line 493 in Realm/ObjectServerTests/RealmServer.swift

View check run for this annotation

Xcode Cloud / RealmSwift | sync_15.3 | Test - macOS

Realm/ObjectServerTests/RealmServer.swift#L493

Static property 'shared' is not concurrency-safe because it is not either conforming to 'Sendable' or isolated to a global actor; this is an error in Swift 6

/// Log level for the server and mongo processes.
public var logLevel = LogLevel.none
Expand Down Expand Up @@ -1187,6 +1187,14 @@
return session.apps[appServerId].users[userId].delete()
}

public func revokeUserSessions(_ appId: String, userId: String) -> Result<Any?, Error> {
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 {
Expand Down
29 changes: 29 additions & 0 deletions Realm/ObjectServerTests/SwiftObjectServerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1322,6 +1322,35 @@ 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()

let didCloseError = watchTestUtility.didCloseError! as NSError
XCTAssertNotNil(didCloseError)
XCTAssertEqual(didCloseError.localizedDescription, "URLSession HTTP error code: 403")
XCTAssertNil(didCloseError.userInfo[NSUnderlyingErrorKey])
}
}

#endif // os(macOS)
13 changes: 11 additions & 2 deletions Realm/ObjectServerTests/WatchTestUtility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ final public class WatchTestUtility: ChangeEventDelegate {
private let openExpectation: XCTestExpectation
private let closeExpectation: XCTestExpectation
private var changeExpectation: XCTestExpectation?
private let expectError: Bool
public var didCloseError: 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")
}
Expand Down Expand Up @@ -57,7 +60,13 @@ final public class WatchTestUtility: ChangeEventDelegate {
}

public func changeStreamDidClose(with error: Error?) {
XCTAssertNil(error)
if expectError {
XCTAssertNotNil(error)
} else {
XCTAssertNil(error)
}

didCloseError = error
closeExpectation.fulfill()
}

Expand Down
6 changes: 4 additions & 2 deletions Realm/RLMNetworkTransport.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}

Expand Down
Loading