Skip to content

Commit

Permalink
RSA4a (#517)
Browse files Browse the repository at this point in the history
* Test suite: add TestProxyHTTPExecutor.simulateIncomingServerErrorOnNextRequest

* RSA4a

* Fix Realtime: indicate an error and not retry the request when the server responds with a token error
  • Loading branch information
ricardopereira committed Nov 30, 2016
1 parent c19e4ed commit 312f5c7
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 10 deletions.
22 changes: 15 additions & 7 deletions Source/ARTRealtime.m
Original file line number Diff line number Diff line change
Expand Up @@ -461,11 +461,8 @@ - (void)onDisconnected {

- (void)onDisconnected:(ARTProtocolMessage *)message {
[self.logger info:@"R:%p ARTRealtime disconnected", self];
ARTErrorInfo *error;
if (message) {
error = message.error;
}
if (!_renewingToken && error && error.statusCode == 401 && error.code >= 40140 && error.code < 40150 && [self isTokenRenewable]) {
ARTErrorInfo *error = message.error;
if ([self shouldRenewToken:&error]) {
[self connectWithRenewedToken];
[self transition:ARTRealtimeDisconnected withErrorInfo:error];
[self.connection setErrorReason:nil];
Expand Down Expand Up @@ -495,13 +492,24 @@ - (void)onError:(ARTProtocolMessage *)message {
[self onChannelMessage:message];
} else {
ARTErrorInfo *error = message.error;
if (!_renewingToken && error && error.statusCode == 401 && error.code >= 40140 && error.code < 40150 && [self isTokenRenewable]) {
if ([self shouldRenewToken:&error]) {
[self connectWithRenewedToken];
return;
}
[self.connection setId:nil];
[self transition:ARTRealtimeFailed withErrorInfo:message.error];
[self transition:ARTRealtimeFailed withErrorInfo:error];
}
}

- (BOOL)shouldRenewToken:(ARTErrorInfo **)errorPtr {
if (!_renewingToken && errorPtr && *errorPtr &&
(*errorPtr).statusCode == 401 && (*errorPtr).code >= 40140 && (*errorPtr).code < 40150) {
if ([self isTokenRenewable]) {
return YES;
}
*errorPtr = [ARTErrorInfo createWithCode:ARTStateRequestTokenFailed message:@"no means to renew the token is provided (either an API key, authCallback or authUrl)"];
}
return NO;
}

- (BOOL)isTokenRenewable {
Expand Down
60 changes: 60 additions & 0 deletions Spec/Auth.swift
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,66 @@ class Auth : QuickSpec {
expect(client.auth.method).to(equal(ARTAuthMethod.Token))
}
}

// RSA4a
it("should indicate an error and not retry the request when the server responds with a token error and there is no way to renew the token") {
let options = AblyTests.clientOptions()
options.token = getTestToken()

let rest = ARTRest(options: options)
// No means to renew the token is provided
expect(rest.options.key).to(beNil())
expect(rest.options.authCallback).to(beNil())
expect(rest.options.authUrl).to(beNil())
rest.httpExecutor = testHTTPExecutor

let channel = rest.channels.get("test")

testHTTPExecutor.simulateIncomingServerErrorOnNextRequest(40141, description: "token revoked")
waitUntil(timeout: testTimeout) { done in
channel.publish("message", data: nil) { error in
guard let error = error else {
fail("Error is nil"); done(); return
}
expect(UInt(error.code)).to(equal(ARTState.RequestTokenFailed.rawValue))
done()
}
}
}

// RSA4a
it("should transition the connection to the FAILED state when the server responds with a token error and there is no way to renew the token") {
let options = AblyTests.clientOptions()
options.tokenDetails = getTestTokenDetails(ttl: 0.1)
options.autoConnect = false

// Token will expire, expecting 40142
waitUntil(timeout: testTimeout) { done in
delay(0.2) { done() }
}

let realtime = ARTRealtime(options: options)
defer { realtime.dispose(); realtime.close() }
// No means to renew the token is provided
expect(realtime.options.key).to(beNil())
expect(realtime.options.authCallback).to(beNil())
expect(realtime.options.authUrl).to(beNil())
realtime.setTransportClass(TestProxyTransport.self)

let channel = realtime.channels.get("test")

waitUntil(timeout: testTimeout) { done in
realtime.connect()
channel.publish("message", data: nil) { error in
guard let error = error else {
fail("Error is nil"); done(); return
}
expect(UInt(error.code)).to(equal(ARTState.RequestTokenFailed.rawValue))
expect(realtime.connection.state).to(equal(ARTRealtimeConnectionState.Failed))
done()
}
}
}
}

// RSA14
Expand Down
4 changes: 2 additions & 2 deletions Spec/RealtimeClientConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1725,7 +1725,7 @@ class RealtimeClientConnection: QuickSpec {
guard let errorInfo = errorInfo else {
fail("ErrorInfo is nil"); done(); return
}
expect(errorInfo.code).to(equal(40142)) //Token expired
expect(UInt(errorInfo.code)).to(equal(ARTState.RequestTokenFailed.rawValue))
done()
default:
break
Expand Down Expand Up @@ -2422,7 +2422,7 @@ class RealtimeClientConnection: QuickSpec {
guard let error = stateChange?.reason else {
fail("Error is nil"); done(); return
}
expect(error.code) == 40142
expect(UInt(error.code)).to(equal(ARTState.RequestTokenFailed.rawValue))
expect(client.connection.errorReason).to(beIdenticalTo(error))
done()
}
Expand Down
42 changes: 41 additions & 1 deletion Spec/TestUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -577,9 +577,38 @@ class MockHTTP: ARTHttp {
/// Records each request and response for test purpose.
class TestProxyHTTPExecutor: NSObject, ARTHTTPExecutor {

struct ErrorSimulator {
let value: Int
let description: String
let serverId = "server-test-suite"

mutating func stubResponse(url: NSURL) -> NSHTTPURLResponse? {
return NSHTTPURLResponse(URL: url, statusCode: 401, HTTPVersion: "HTTP/1.1", headerFields: [
"Content-Length": String(stubData?.length ?? 0),
"Content-Type": "application/json",
"X-Ably-Errorcode": String(value),
"X-Ably-Errormessage": description,
"X-Ably-Serverid": serverId,
]
)
}

lazy var stubData: NSData? = {
let jsonObject = ["error": [
"statusCode": modf(Float(self.value)/100).0, //whole number part
"code": self.value,
"message": self.description,
"serverId": self.serverId,
]
]
return try? NSJSONSerialization.dataWithJSONObject(jsonObject, options: NSJSONWritingOptions.init(rawValue: 0))
}()
}
private var errorSimulator: ErrorSimulator?

var http: ARTHttp? = ARTHttp()
var logger: ARTLog?

var requests: [NSMutableURLRequest] = []
var responses: [NSHTTPURLResponse] = []

Expand All @@ -592,6 +621,13 @@ class TestProxyHTTPExecutor: NSObject, ARTHTTPExecutor {
return
}
self.requests.append(request)

if var simulatedError = errorSimulator, requestURL = request.URL {
defer { errorSimulator = nil }
callback?(simulatedError.stubResponse(requestURL), simulatedError.stubData, nil)
return
}

if let performEvent = beforeRequest {
performEvent(request, callback)
}
Expand All @@ -611,6 +647,10 @@ class TestProxyHTTPExecutor: NSObject, ARTHTTPExecutor {
}
}

func simulateIncomingServerErrorOnNextRequest(errorValue: Int, description: String) {
errorSimulator = ErrorSimulator(value: errorValue, description: description, stubData: nil)
}

}

/// Records each message for test purpose.
Expand Down

0 comments on commit 312f5c7

Please sign in to comment.