Skip to content

Commit

Permalink
RCOCOA-2310: Fix a crash when receiving 401/403 when opening a watch …
Browse files Browse the repository at this point in the history
…stream (#8536)

* Fix a crash when error is nil for watch streams

* var -> let

* Assert underlying error is nil

* Fix lint violations
  • Loading branch information
nirinchev committed Apr 8, 2024
1 parent d1906cd commit 2974253
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 5 deletions.
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 @@ -1187,6 +1187,14 @@ public class RealmServer: NSObject {
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

0 comments on commit 2974253

Please sign in to comment.