Skip to content

Commit

Permalink
RSA4b (#518)
Browse files Browse the repository at this point in the history
* RSA4b

* Fix REST: should retry the request once if a token error occurs

* Fix Realtime: if the token creation failed then the connection should move to the DISCONNECTED state and reports the error
  • Loading branch information
ricardopereira authored Dec 5, 2016
1 parent dabeea6 commit 89a0137
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 9 deletions.
6 changes: 3 additions & 3 deletions Source/ARTRealtime.m
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ - (void)transitionSideEffects:(ARTConnectionStateChange *)stateChange {
[_transport connect];
}

if (self.connection.state != ARTRealtimeFailed && self.connection.state != ARTRealtimeClosed) {
if (self.connection.state != ARTRealtimeFailed && self.connection.state != ARTRealtimeClosed && self.connection.state != ARTRealtimeDisconnected) {
[_reachability listenForHost:[_transport host] callback:^(BOOL reachable) {
if (reachable) {
switch (_connection.state) {
Expand Down Expand Up @@ -915,7 +915,7 @@ - (void)realtimeTransportClosed:(id<ARTRealtimeTransport>)transport {
[self transition:ARTRealtimeClosed];
}

- (void)realtimeTransportDisconnected:(id<ARTRealtimeTransport>)transport {
- (void)realtimeTransportDisconnected:(id<ARTRealtimeTransport>)transport withError:(ARTRealtimeTransportError *)error {
if (transport != self.transport) {
// Old connection
return;
Expand All @@ -924,7 +924,7 @@ - (void)realtimeTransportDisconnected:(id<ARTRealtimeTransport>)transport {
if (self.connection.state == ARTRealtimeClosing) {
[self transition:ARTRealtimeClosed];
} else {
[self transition:ARTRealtimeDisconnected];
[self transition:ARTRealtimeDisconnected withErrorInfo:[ARTErrorInfo createWithNSError:error.error]];
}
}

Expand Down
2 changes: 1 addition & 1 deletion Source/ARTRealtimeTransport.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ typedef NS_ENUM(NSUInteger, ARTRealtimeTransportState) {
- (void)realtimeTransportUnavailable:(id<ARTRealtimeTransport>)transport;

- (void)realtimeTransportClosed:(id<ARTRealtimeTransport>)transport;
- (void)realtimeTransportDisconnected:(id<ARTRealtimeTransport>)transport;
- (void)realtimeTransportDisconnected:(id<ARTRealtimeTransport>)transport withError:(art_nullable ARTRealtimeTransportError *)error;
- (void)realtimeTransportNeverConnected:(id<ARTRealtimeTransport>)transport;
- (void)realtimeTransportRefused:(id<ARTRealtimeTransport>)transport;
- (void)realtimeTransportTooBig:(id<ARTRealtimeTransport>)transport;
Expand Down
20 changes: 18 additions & 2 deletions Source/ARTRest.m
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@
#import "ARTDefault.h"
#import "ARTGCD.h"

@interface ARTRest () {
__block NSUInteger _tokenErrorRetries;
}

@end

@implementation ARTRest

- (instancetype)initWithOptions:(ARTClientOptions *)options {
Expand Down Expand Up @@ -68,6 +74,7 @@ - (instancetype)initWithOptions:(ARTClientOptions *)options {
};
_defaultEncoding = (_options.useBinaryProtocol ? [msgPackEncoder mimeType] : [jsonEncoder mimeType]);
_fallbackCount = 0;
_tokenErrorRetries = 0;

_auth = [[ARTAuth alloc] init:self withOptions:_options];
_channels = [[ARTRestChannels alloc] initWithRest:self];
Expand Down Expand Up @@ -97,9 +104,15 @@ - (void)executeRequest:(NSMutableURLRequest *)request withAuthOption:(ARTAuthent
[self executeRequest:request completion:callback];
break;
case ARTAuthenticationOn:
_tokenErrorRetries = 0;
[self executeRequestWithAuthentication:request withMethod:self.auth.method force:NO completion:callback];
break;
case ARTAuthenticationNewToken:
_tokenErrorRetries = 0;
[self executeRequestWithAuthentication:request withMethod:self.auth.method force:YES completion:callback];
break;
case ARTAuthenticationTokenRetry:
_tokenErrorRetries = _tokenErrorRetries + 1;
[self executeRequestWithAuthentication:request withMethod:self.auth.method force:YES completion:callback];
break;
case ARTAuthenticationUseBasic:
Expand Down Expand Up @@ -167,8 +180,11 @@ - (void)executeRequest:(NSMutableURLRequest *)request completion:(void (^)(NSHTT
if ([self shouldRenewToken:&dataError]) {
[self.logger debug:__FILE__ line:__LINE__ message:@"RS:%p retry request %@", self, request];
// Make a single attempt to reissue the token and resend the request
[self executeRequest:request withAuthOption:ARTAuthenticationNewToken completion:callback];
return;
if (_tokenErrorRetries < 1) {
[self executeRequest:request withAuthOption:ARTAuthenticationTokenRetry completion:callback];
return;
}
error = dataError;
} else {
// Return error with HTTP StatusCode if ARTErrorStatusCode does not exist
if (!dataError) {
Expand Down
3 changes: 2 additions & 1 deletion Source/ARTTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ typedef NS_ENUM(NSUInteger, ARTAuthentication) {
ARTAuthenticationOff,
ARTAuthenticationOn,
ARTAuthenticationUseBasic,
ARTAuthenticationNewToken
ARTAuthenticationNewToken,
ARTAuthenticationTokenRetry
};

typedef NS_ENUM(NSUInteger, ARTAuthMethod) {
Expand Down
11 changes: 9 additions & 2 deletions Source/ARTWebSocketTransport.m
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,14 @@ - (void)connectForcingNewToken:(BOOL)forceNewToken {

if (error) {
[[weakSelf logger] error:@"R:%p WS:%p ARTWebSocketTransport: token auth failed with %@", _delegate, self, error.description];
[[weakSelf delegate] realtimeTransportFailed:weakSelf withError:[[ARTRealtimeTransportError alloc] initWithError:error type:ARTRealtimeTransportErrorTypeAuth url:self.websocketURL]];
if (error.code == 40102 /*incompatible credentials*/) {
// RSA15c
[[weakSelf delegate] realtimeTransportFailed:weakSelf withError:[[ARTRealtimeTransportError alloc] initWithError:error type:ARTRealtimeTransportErrorTypeAuth url:self.websocketURL]];
}
else {
// RSA4b
[[weakSelf delegate] realtimeTransportDisconnected:weakSelf withError:[[ARTRealtimeTransportError alloc] initWithError:error type:ARTRealtimeTransportErrorTypeAuth url:self.websocketURL]];
}
return;
}

Expand Down Expand Up @@ -301,7 +308,7 @@ - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reas
case ARTWsGoingAway:
case ARTWsAbnormalClose:
// Connectivity issue
[s.delegate realtimeTransportDisconnected:s];
[s.delegate realtimeTransportDisconnected:s withError:nil];
break;
case ARTWsRefuse:
case ARTWsPolicyValidation:
Expand Down
83 changes: 83 additions & 0 deletions Spec/Auth.swift
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,89 @@ class Auth : QuickSpec {
}
}
}

// RSA4b
it("in REST, if the token creation failed or the subsequent request with the new token failed due to a token error, then the request should result in an error") {
let options = AblyTests.commonAppSetup()
options.useTokenAuth = true

let rest = ARTRest(options: options)
rest.httpExecutor = testHTTPExecutor

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

testHTTPExecutor.afterRequest = { _ in
testHTTPExecutor.simulateIncomingServerErrorOnNextRequest(40141, description: "token revoked")
}

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(error.code).to(equal(40141))
done()
}
}

// First request and a second attempt
expect(testHTTPExecutor.requests).to(haveCount(2))
}

// RSA4b
it("in Realtime, if the token creation failed then the connection should move to the DISCONNECTED state and reports the error") {
let options = AblyTests.commonAppSetup()
options.authCallback = { tokenParams, completion in
completion(nil, NSError(domain: NSURLErrorDomain, code: -1003, userInfo: [NSLocalizedDescriptionKey: "A server with the specified hostname could not be found."]))
}
options.autoConnect = false

let realtime = ARTRealtime(options: options)
defer { realtime.dispose(); realtime.close() }

waitUntil(timeout: testTimeout) { done in
realtime.connection.once(.Failed) { _ in
fail("Should not reach Failed state"); done(); return
}
realtime.connection.once(.Disconnected) { stateChange in
guard let errorInfo = stateChange?.reason else {
fail("ErrorInfo is nil"); done(); return
}
expect(errorInfo.message).to(contain("server with the specified hostname could not be found"))
done()
}
realtime.connect()
}
}

// RSA4b
it("in Realtime, if the connection fails due to a terminal token error, then the connection should move to the FAILED state and reports the error") {
let options = AblyTests.commonAppSetup()
options.authCallback = { tokenParams, completion in
let token = getTestToken()
let invalidToken = String(token.characters.reverse())
completion(invalidToken, nil)
}
options.autoConnect = false

let realtime = ARTRealtime(options: options)
defer { realtime.dispose(); realtime.close() }

waitUntil(timeout: testTimeout) { done in
realtime.connection.once(.Failed) { stateChange in
guard let errorInfo = stateChange?.reason else {
fail("ErrorInfo is nil"); done(); return
}
expect(errorInfo.message).to(contain("No application found with id"))
done()
}
realtime.connection.once(.Disconnected) { _ in
fail("Should not reach Disconnected state"); done(); return
}
realtime.connect()
}
}
}

// RSA14
Expand Down

0 comments on commit 89a0137

Please sign in to comment.