From f640e68ab59e0404697c4e546eea7135dc5abc0a Mon Sep 17 00:00:00 2001 From: Evgeny Karkan Date: Tue, 6 Sep 2016 16:40:17 +0300 Subject: [PATCH 1/7] Implemented timeOffset logic. --- Source/ARTAuth+Private.h | 1 + Source/ARTAuth.h | 2 ++ Source/ARTAuth.m | 41 ++++++++++++++++++++++++++++++---------- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/Source/ARTAuth+Private.h b/Source/ARTAuth+Private.h index f28c623e8..4ec055f52 100644 --- a/Source/ARTAuth+Private.h +++ b/Source/ARTAuth+Private.h @@ -17,6 +17,7 @@ ART_ASSUME_NONNULL_BEGIN @property (nonatomic, weak) ARTLog *logger; @property (art_nullable, nonatomic, readonly, strong) ARTTokenDetails *tokenDetails; +@property (art_nullable, nonatomic, strong) NSNumber *timeOffset; @end diff --git a/Source/ARTAuth.h b/Source/ARTAuth.h index a2a6bd6f4..96ae1984a 100644 --- a/Source/ARTAuth.h +++ b/Source/ARTAuth.h @@ -49,6 +49,8 @@ ART_ASSUME_NONNULL_BEGIN - (void)createTokenRequest:(art_nullable ARTTokenParams *)tokenParams options:(art_nullable ARTAuthOptions *)options callback:(void (^)(ARTTokenRequest *__art_nullable tokenRequest, NSError *__art_nullable error))callback; +- (void)discardTimeOffset; + @end ART_ASSUME_NONNULL_END diff --git a/Source/ARTAuth.m b/Source/ARTAuth.m index d914cf885..179ecc6c5 100644 --- a/Source/ARTAuth.m +++ b/Source/ARTAuth.m @@ -356,14 +356,25 @@ - (void)createTokenRequest:(ARTTokenParams *)tokenParams options:(ARTAuthOptions } if (replacedOptions.queryTime) { - [_rest time:^(NSDate *time, NSError *error) { - if (error) { - callback(nil, error); - } else { - currentTokenParams.timestamp = [self handleServerTime:time]; - callback([currentTokenParams sign:replacedOptions.key], nil); - } - }]; + if (self.timeOffset == nil) { + [_rest time:^(NSDate *time, NSError *error) { + if (error) { + callback(nil, error); + } else { + NSDate *dateNow = [NSDate date]; + long long nowMillis = [dateNow timeIntervalSince1970]*1000; + long long serverNowMillis = [time timeIntervalSince1970]*1000; + self.timeOffset = [NSNumber numberWithLongLong:(serverNowMillis - nowMillis)]; + + currentTokenParams.timestamp = [self timeStampDateWithDateNow:dateNow]; + callback([currentTokenParams sign:replacedOptions.key], nil); + } + }]; + } + else { + currentTokenParams.timestamp = [self timeStampDateWithDateNow:[NSDate date]]; + callback([currentTokenParams sign:replacedOptions.key], nil); + } } else { callback([currentTokenParams sign:replacedOptions.key], nil); } @@ -392,8 +403,18 @@ - (NSString *)getClientId { } } -- (void)setTokenDetails:(ARTTokenDetails *)tokenDetails { - _tokenDetails = tokenDetails; +- (void)discardTimeOffset { + self.timeOffset = nil; +} + +#pragma mark - Helper + +- (NSDate*)timeStampDateWithDateNow:(NSDate*)nowDate { + long long appNow = [nowDate timeIntervalSince1970]*1000; + long long seconds = ([self.timeOffset longLongValue] + appNow)/1000; + NSDate *timestampDate = [NSDate dateWithTimeIntervalSince1970:seconds]; + + return timestampDate; } @end From b172af7851cb33a3d74cbf98779c10a261db1fd7 Mon Sep 17 00:00:00 2001 From: Evgeny Karkan Date: Tue, 6 Sep 2016 19:22:16 +0300 Subject: [PATCH 2/7] RSA10k Added offset test. Test improvement, use of fake time offset. --- Spec/Auth.swift | 149 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 148 insertions(+), 1 deletion(-) diff --git a/Spec/Auth.swift b/Spec/Auth.swift index a5f384a3f..ad951782c 100644 --- a/Spec/Auth.swift +++ b/Spec/Auth.swift @@ -2379,7 +2379,154 @@ class Auth : QuickSpec { } } - + + // RSA10k + context("should adhere to all requirements relating to") { + it("Should obtain server time once and persist the offset from the local clock") { + let rest = ARTRest(options: AblyTests.commonAppSetup()) + + var serverTimeRequestWasMade = false + let block: @convention(block) (AspectInfo) -> Void = { _ in + serverTimeRequestWasMade = true + } + + let hook = ARTRest.aspect_hookSelector(rest) + // Adds a block of code after `time` is triggered + let _ = try? hook(#selector(ARTRest.time(_:)), withOptions: .PositionAfter, usingBlock: unsafeBitCast(block, ARTRest.self)) + + let authOptions = ARTAuthOptions() + authOptions.queryTime = true + + waitUntil(timeout: testTimeout) { done in + rest.auth.createTokenRequest(nil, options: authOptions, callback: { tokenRequest, error in + expect(error).to(beNil()) + guard let tokenRequest = tokenRequest else { + XCTFail("TokenRequest is nil"); return + } + expect(tokenRequest.timestamp).toNot(beNil()) + expect(serverTimeRequestWasMade).to(beTrue()) + expect(rest.auth.timeOffset).toNot(beNil()) + done() + }) + } + + var serverTimeRequestWasMade2 = false + let block2: @convention(block) (AspectInfo) -> Void = { _ in + serverTimeRequestWasMade2 = true + } + + let hook2 = ARTRest.aspect_hookSelector(rest) + let _ = try? hook2(#selector(ARTRest.time(_:)), withOptions: .PositionAfter, usingBlock: unsafeBitCast(block2, ARTRest.self)) + + waitUntil(timeout: testTimeout) { done in + rest.auth.createTokenRequest(nil, options: authOptions, callback: { tokenRequest, error in + expect(error).to(beNil()) + guard let tokenRequest = tokenRequest else { + XCTFail("TokenRequest is nil"); return + } + expect(tokenRequest.timestamp).toNot(beNil()) + expect(serverTimeRequestWasMade2).to(beFalse()) + done() + }) + } + } + + it("should be possible by lib Client to discard the cached local clock offset") { + let rest = ARTRest(options: AblyTests.commonAppSetup()) + + let authOptions = ARTAuthOptions() + authOptions.queryTime = true + + waitUntil(timeout: testTimeout) { done in + rest.auth.createTokenRequest(nil, options: authOptions, callback: { tokenRequest, error in + expect(error).to(beNil()) + guard let tokenRequest = tokenRequest else { + XCTFail("tokenRequest is nil"); return + } + expect(rest.auth.timeOffset).toNot(beNil()) + rest.auth.discardTimeOffset() + expect(rest.auth.timeOffset).to(beNil()) + done() + }) + } + + var serverTimeRequestWasMade = false + let block: @convention(block) (AspectInfo) -> Void = { _ in + serverTimeRequestWasMade = true + } + + let hook = ARTRest.aspect_hookSelector(rest) + let _ = try? hook(#selector(ARTRest.time(_:)), withOptions: .PositionAfter, usingBlock: unsafeBitCast(block, ARTRest.self)) + + waitUntil(timeout: testTimeout) { done in + rest.auth.createTokenRequest(nil, options: authOptions, callback: { tokenRequest, error in + expect(error).to(beNil()) + guard let tokenRequest = tokenRequest else { + XCTFail("tokenRequest is nil"); return + } + expect(serverTimeRequestWasMade).to(beTrue()) + done() + }) + } + } + + it("should use the local clock offset to calculate the server time") { + let rest = ARTRest(options: AblyTests.commonAppSetup()) + + let authOptions = AblyTests.commonAppSetup() + authOptions.queryTime = true + + var firstTokenRequestTimeStamp: NSDate? + waitUntil(timeout: testTimeout) { done in + rest.auth.createTokenRequest(nil, options: authOptions, callback: { tokenRequest, error in + expect(error).to(beNil()) + guard let tokenRequest = tokenRequest else { + XCTFail("tokenRequest is nil"); done(); return + } + firstTokenRequestTimeStamp = tokenRequest.timestamp + done() + }) + } + expect(rest.auth.timeOffset).toNot(beNil()) + + waitUntil(timeout: testTimeout) { done in + let nowMillis = CLongLong((NSDate().timeIntervalSince1970 * 1000)) + let requestTimeMillis = CLongLong((rest.auth.timeOffset?.longLongValue)! + nowMillis) + + rest.auth.authorise(nil, options: authOptions) { tokenDetails, error in + expect(error).to(beNil()) + guard let tokenDetails = tokenDetails else { + XCTFail("TokenDetails is nil"); done(); return + } + let issuedMillis = CLongLong((tokenDetails.issued?.timeIntervalSince1970)! * 1000) + expect(issuedMillis >= (requestTimeMillis - 1000)).to(beTrue()) + expect(issuedMillis <= (requestTimeMillis + 1000)).to(beTrue()) + done() + } + } + + //set fake offset to check it affects token request `timestamp` property + let fakeOffset = NSNumber(integer: 20000) //20 seconds + rest.auth.timeOffset = fakeOffset + + var secondTokenRequestTimeStamp: NSDate? + waitUntil(timeout: testTimeout) { done in + rest.auth.createTokenRequest(nil, options: authOptions, callback: { tokenRequest, error in + expect(error).to(beNil()) + guard let tokenRequest = tokenRequest else { + XCTFail("tokenRequest is nil"); done(); return + } + secondTokenRequestTimeStamp = tokenRequest.timestamp + + let firstMillis = firstTokenRequestTimeStamp!.timeIntervalSince1970 * 1000 + let secondMillis = secondTokenRequestTimeStamp!.timeIntervalSince1970 * 1000 + let diff = secondMillis - firstMillis + expect(CLongLong(diff) >= fakeOffset.longLongValue).to(beTrue()) + done() + }) + } + } + } } describe("TokenParams") { From 2cafbce39ebbe04f292589f358606f5efc6e5808 Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Fri, 30 Sep 2016 16:14:18 +0100 Subject: [PATCH 3/7] Fix RSA10k --- Source/ARTAuth+Private.h | 8 +- Source/ARTAuth.h | 2 - Source/ARTAuth.m | 51 ++++---- Spec/Auth.swift | 266 +++++++++++++++++++++++++-------------- 4 files changed, 203 insertions(+), 124 deletions(-) diff --git a/Source/ARTAuth+Private.h b/Source/ARTAuth+Private.h index 4ec055f52..aa9679ae7 100644 --- a/Source/ARTAuth+Private.h +++ b/Source/ARTAuth+Private.h @@ -17,7 +17,7 @@ ART_ASSUME_NONNULL_BEGIN @property (nonatomic, weak) ARTLog *logger; @property (art_nullable, nonatomic, readonly, strong) ARTTokenDetails *tokenDetails; -@property (art_nullable, nonatomic, strong) NSNumber *timeOffset; +@property (nonatomic, readonly, assign) NSTimeInterval timeOffset; @end @@ -35,9 +35,15 @@ ART_ASSUME_NONNULL_BEGIN // CONNECTED ProtocolMessage may contain a clientId - (void)setProtocolClientId:(NSString *)clientId; +// Discard the cached local clock offset +- (void)discardTimeOffset; + // Private TokenDetails setter for testing only - (void)setTokenDetails:(ARTTokenDetails *)tokenDetails; +// Private TimeOffset setter for testing only +- (void)setTimeOffset:(NSTimeInterval)offset; + @end ART_ASSUME_NONNULL_END diff --git a/Source/ARTAuth.h b/Source/ARTAuth.h index 96ae1984a..a2a6bd6f4 100644 --- a/Source/ARTAuth.h +++ b/Source/ARTAuth.h @@ -49,8 +49,6 @@ ART_ASSUME_NONNULL_BEGIN - (void)createTokenRequest:(art_nullable ARTTokenParams *)tokenParams options:(art_nullable ARTAuthOptions *)options callback:(void (^)(ARTTokenRequest *__art_nullable tokenRequest, NSError *__art_nullable error))callback; -- (void)discardTimeOffset; - @end ART_ASSUME_NONNULL_END diff --git a/Source/ARTAuth.m b/Source/ARTAuth.m index 179ecc6c5..5e8ef9146 100644 --- a/Source/ARTAuth.m +++ b/Source/ARTAuth.m @@ -95,6 +95,8 @@ - (void)storeOptions:(ARTAuthOptions *)customOptions { self.options.authMethod = customOptions.authMethod; self.options.authParams = [customOptions.authParams copy]; self.options.useTokenAuth = customOptions.useTokenAuth; + self.options.queryTime = false; + self.options.force = false; } - (ARTTokenParams *)mergeParams:(ARTTokenParams *)customParams { @@ -154,7 +156,7 @@ - (void)requestToken:(ARTTokenParams *)tokenParams withOptions:(ARTAuthOptions * // The values replace all corresponding. ARTAuthOptions *replacedOptions = authOptions ? authOptions : self.options; ARTTokenParams *currentTokenParams = tokenParams ? tokenParams : _tokenParams; - tokenParams.timestamp = [NSDate date]; + tokenParams.timestamp = [self currentDate]; if (replacedOptions.key == nil && replacedOptions.authCallback == nil && replacedOptions.authUrl == nil) { callback(nil, [ARTErrorInfo createWithCode:ARTStateRequestTokenFailed message:@"no means to renew the token is provided (either an API key, authCallback or authUrl)"]); @@ -279,11 +281,11 @@ - (void)authorise:(ARTTokenParams *)tokenParams options:(ARTAuthOptions *)authOp ARTAuthOptions *replacedOptions; if ([authOptions isOnlyForceTrue]) { - replacedOptions = self.options; + replacedOptions = [self.options copy]; replacedOptions.force = YES; } else { - replacedOptions = authOptions ? : self.options; + replacedOptions = [authOptions copy] ? : [self.options copy]; } [self storeOptions:replacedOptions]; @@ -296,7 +298,7 @@ - (void)authorise:(ARTTokenParams *)tokenParams options:(ARTAuthOptions *)authOp [self.logger verbose:@"RS:%p ARTAuth: reuse current token.", _rest]; requestNewToken = NO; } - else if ([self.tokenDetails.expires timeIntervalSinceNow] > 0) { + else if ([self.tokenDetails.expires timeIntervalSinceDate:[self currentDate]] > 0) { [self.logger verbose:@"RS:%p ARTAuth: current token has not expired yet. Reusing token details.", _rest]; requestNewToken = NO; } @@ -355,28 +357,25 @@ - (void)createTokenRequest:(ARTTokenParams *)tokenParams options:(ARTAuthOptions return; } - if (replacedOptions.queryTime) { - if (self.timeOffset == nil) { + if (_timeOffset && !replacedOptions.queryTime) { + currentTokenParams.timestamp = [self currentDate]; + callback([currentTokenParams sign:replacedOptions.key], nil); + } + else { + if (replacedOptions.queryTime) { [_rest time:^(NSDate *time, NSError *error) { if (error) { callback(nil, error); } else { - NSDate *dateNow = [NSDate date]; - long long nowMillis = [dateNow timeIntervalSince1970]*1000; - long long serverNowMillis = [time timeIntervalSince1970]*1000; - self.timeOffset = [NSNumber numberWithLongLong:(serverNowMillis - nowMillis)]; - - currentTokenParams.timestamp = [self timeStampDateWithDateNow:dateNow]; + NSDate *serverTime = [self handleServerTime:time]; + _timeOffset = [serverTime timeIntervalSinceNow]; + currentTokenParams.timestamp = serverTime; callback([currentTokenParams sign:replacedOptions.key], nil); } }]; - } - else { - currentTokenParams.timestamp = [self timeStampDateWithDateNow:[NSDate date]]; + } else { callback([currentTokenParams sign:replacedOptions.key], nil); } - } else { - callback([currentTokenParams sign:replacedOptions.key], nil); } } @@ -403,18 +402,20 @@ - (NSString *)getClientId { } } +- (NSDate*)currentDate { + return [[NSDate date] dateByAddingTimeInterval:_timeOffset]; +} + - (void)discardTimeOffset { - self.timeOffset = nil; + _timeOffset = 0; } -#pragma mark - Helper +- (void)setTokenDetails:(ARTTokenDetails *)tokenDetails { + _tokenDetails = tokenDetails; +} -- (NSDate*)timeStampDateWithDateNow:(NSDate*)nowDate { - long long appNow = [nowDate timeIntervalSince1970]*1000; - long long seconds = ([self.timeOffset longLongValue] + appNow)/1000; - NSDate *timestampDate = [NSDate dateWithTimeIntervalSince1970:seconds]; - - return timestampDate; +- (void)setTimeOffset:(NSTimeInterval)offset { + _timeOffset = offset; } @end diff --git a/Spec/Auth.swift b/Spec/Auth.swift index ad951782c..b551d8c2c 100644 --- a/Spec/Auth.swift +++ b/Spec/Auth.swift @@ -2381,151 +2381,225 @@ class Auth : QuickSpec { } // RSA10k - context("should adhere to all requirements relating to") { - it("Should obtain server time once and persist the offset from the local clock") { - let rest = ARTRest(options: AblyTests.commonAppSetup()) - - var serverTimeRequestWasMade = false - let block: @convention(block) (AspectInfo) -> Void = { _ in - serverTimeRequestWasMade = true + context("server time offset") { + + it("should obtain server time once and persist the offset from the local clock") { + let options = AblyTests.commonAppSetup() + let rest = ARTRest(options: options) + + var serverDate: NSDate? + waitUntil(timeout: testTimeout) { done in + rest.time { date, error in + expect(error).to(beNil()) + serverDate = date + done() + } } - - let hook = ARTRest.aspect_hookSelector(rest) - // Adds a block of code after `time` is triggered - let _ = try? hook(#selector(ARTRest.time(_:)), withOptions: .PositionAfter, usingBlock: unsafeBitCast(block, ARTRest.self)) - + + var serverTimeRequestCount = 0 + let hook = rest.testSuite_injectIntoMethodAfter(#selector(rest.time(_:))) { + serverTimeRequestCount += 1 + } + defer { hook.remove() } + let authOptions = ARTAuthOptions() + authOptions.key = options.key authOptions.queryTime = true - + waitUntil(timeout: testTimeout) { done in - rest.auth.createTokenRequest(nil, options: authOptions, callback: { tokenRequest, error in + rest.auth.authorise(nil, options: authOptions, callback: { tokenDetails, error in expect(error).to(beNil()) - guard let tokenRequest = tokenRequest else { - XCTFail("TokenRequest is nil"); return + guard let tokenDetails = tokenDetails else { + fail("TokenDetails is nil"); done(); return } - expect(tokenRequest.timestamp).toNot(beNil()) - expect(serverTimeRequestWasMade).to(beTrue()) - expect(rest.auth.timeOffset).toNot(beNil()) + guard let serverDate = serverDate else { + fail("ServerDate is nil"); done(); return + } + expect(rest.auth.timeOffset).toNot(beCloseTo(0)) + expect(tokenDetails.issued).to(beCloseTo(NSDate().dateByAddingTimeInterval(rest.auth.timeOffset), within: 1.0)) + expect(tokenDetails.issued).to(beCloseTo(serverDate, within: 1.0)) + expect(tokenDetails.expires).to(beCloseTo(serverDate.dateByAddingTimeInterval(ARTDefault.ttl()), within: 1.0)) + expect(serverTimeRequestCount) == 1 done() }) } - - var serverTimeRequestWasMade2 = false - let block2: @convention(block) (AspectInfo) -> Void = { _ in - serverTimeRequestWasMade2 = true - } - - let hook2 = ARTRest.aspect_hookSelector(rest) - let _ = try? hook2(#selector(ARTRest.time(_:)), withOptions: .PositionAfter, usingBlock: unsafeBitCast(block2, ARTRest.self)) - + + rest.auth.testSuite_forceTokenToExpire() + waitUntil(timeout: testTimeout) { done in - rest.auth.createTokenRequest(nil, options: authOptions, callback: { tokenRequest, error in + rest.auth.authorise(nil, options: nil) { tokenDetails, error in expect(error).to(beNil()) - guard let tokenRequest = tokenRequest else { - XCTFail("TokenRequest is nil"); return + guard let tokenDetails = tokenDetails else { + fail("TokenDetails is nil"); done(); return } - expect(tokenRequest.timestamp).toNot(beNil()) - expect(serverTimeRequestWasMade2).to(beFalse()) + expect(rest.auth.timeOffset).toNot(beCloseTo(0)) + expect(tokenDetails.issued).to(beCloseTo(NSDate().dateByAddingTimeInterval(rest.auth.timeOffset), within: 1.0)) + let assumedServerDate = NSDate().dateByAddingTimeInterval(rest.auth.timeOffset) + expect(tokenDetails.expires).to(beCloseTo(assumedServerDate.dateByAddingTimeInterval(ARTDefault.ttl()), within: 1.0)) + expect(serverTimeRequestCount) == 2 done() - }) + } } } - - it("should be possible by lib Client to discard the cached local clock offset") { - let rest = ARTRest(options: AblyTests.commonAppSetup()) - + + it("should be consistent the timestamp request with the server time") { + let options = AblyTests.commonAppSetup() + let rest = ARTRest(options: options) + + var serverDate: NSDate? + waitUntil(timeout: testTimeout) { done in + rest.time { date, error in + expect(error).to(beNil()) + serverDate = date + done() + } + } + + var serverTimeRequestCount = 0 + let hook = rest.testSuite_injectIntoMethodAfter(#selector(rest.time(_:))) { + serverTimeRequestCount += 1 + } + defer { hook.remove() } + let authOptions = ARTAuthOptions() + authOptions.key = options.key authOptions.queryTime = true - + waitUntil(timeout: testTimeout) { done in - rest.auth.createTokenRequest(nil, options: authOptions, callback: { tokenRequest, error in + rest.auth.createTokenRequest(nil, options: authOptions) { tokenRequest, error in expect(error).to(beNil()) guard let tokenRequest = tokenRequest else { - XCTFail("tokenRequest is nil"); return + fail("TokenRequest is nil"); done(); return } - expect(rest.auth.timeOffset).toNot(beNil()) - rest.auth.discardTimeOffset() - expect(rest.auth.timeOffset).to(beNil()) + guard let serverDate = serverDate else { + fail("ServerDate is nil"); done(); return + } + expect(rest.auth.timeOffset).toNot(beCloseTo(0)) + expect(tokenRequest.timestamp).to(beCloseTo(NSDate().dateByAddingTimeInterval(rest.auth.timeOffset), within: 0.1)) + expect(tokenRequest.timestamp).to(beCloseTo(serverDate, within: 0.5)) + expect(serverTimeRequestCount) == 1 done() - }) + } } - - var serverTimeRequestWasMade = false - let block: @convention(block) (AspectInfo) -> Void = { _ in - serverTimeRequestWasMade = true + } + + it("should be possible by lib Client to discard the cached local clock offset") { + let options = AblyTests.commonAppSetup() + options.queryTime = true + let rest = ARTRest(options: options) + + var serverTimeRequestCount = 0 + let hook = rest.testSuite_injectIntoMethodAfter(#selector(rest.time(_:))) { + serverTimeRequestCount += 1 } - - let hook = ARTRest.aspect_hookSelector(rest) - let _ = try? hook(#selector(ARTRest.time(_:)), withOptions: .PositionAfter, usingBlock: unsafeBitCast(block, ARTRest.self)) - + defer { hook.remove() } + waitUntil(timeout: testTimeout) { done in - rest.auth.createTokenRequest(nil, options: authOptions, callback: { tokenRequest, error in + rest.auth.authorise(nil, options: nil) { tokenDetails, error in expect(error).to(beNil()) - guard let tokenRequest = tokenRequest else { - XCTFail("tokenRequest is nil"); return + guard let tokenDetails = tokenDetails else { + fail("TokenDetails is nil"); done(); return } - expect(serverTimeRequestWasMade).to(beTrue()) + expect(rest.auth.timeOffset).toNot(beCloseTo(0)) + let assumedServerDate = NSDate().dateByAddingTimeInterval(rest.auth.timeOffset) + expect(tokenDetails.expires).to(beCloseTo(assumedServerDate.dateByAddingTimeInterval(ARTDefault.ttl()), within: 1.0)) + expect(serverTimeRequestCount) == 1 done() - }) + } } - } - - it("should use the local clock offset to calculate the server time") { - let rest = ARTRest(options: AblyTests.commonAppSetup()) - - let authOptions = AblyTests.commonAppSetup() - authOptions.queryTime = true - - var firstTokenRequestTimeStamp: NSDate? + + rest.auth.discardTimeOffset() + expect(rest.auth.timeOffset) == 0 + + rest.auth.testSuite_forceTokenToExpire() + waitUntil(timeout: testTimeout) { done in - rest.auth.createTokenRequest(nil, options: authOptions, callback: { tokenRequest, error in + rest.auth.authorise(nil, options: nil) { tokenDetails, error in expect(error).to(beNil()) - guard let tokenRequest = tokenRequest else { - XCTFail("tokenRequest is nil"); done(); return + guard let tokenDetails = tokenDetails else { + fail("TokenDetails is nil"); done(); return } - firstTokenRequestTimeStamp = tokenRequest.timestamp + expect(rest.auth.timeOffset).toNot(beCloseTo(0)) + let assumedServerDate = NSDate().dateByAddingTimeInterval(rest.auth.timeOffset) + expect(tokenDetails.expires).to(beCloseTo(assumedServerDate.dateByAddingTimeInterval(ARTDefault.ttl()), within: 1.0)) + expect(serverTimeRequestCount) == 2 done() - }) + } } - expect(rest.auth.timeOffset).toNot(beNil()) - + + rest.auth.testSuite_forceTokenToExpire() + + let authOptions = ARTAuthOptions() + authOptions.key = options.key + authOptions.queryTime = false + waitUntil(timeout: testTimeout) { done in - let nowMillis = CLongLong((NSDate().timeIntervalSince1970 * 1000)) - let requestTimeMillis = CLongLong((rest.auth.timeOffset?.longLongValue)! + nowMillis) - rest.auth.authorise(nil, options: authOptions) { tokenDetails, error in expect(error).to(beNil()) guard let tokenDetails = tokenDetails else { - XCTFail("TokenDetails is nil"); done(); return + fail("TokenDetails is nil"); done(); return } - let issuedMillis = CLongLong((tokenDetails.issued?.timeIntervalSince1970)! * 1000) - expect(issuedMillis >= (requestTimeMillis - 1000)).to(beTrue()) - expect(issuedMillis <= (requestTimeMillis + 1000)).to(beTrue()) + expect(rest.auth.timeOffset) == 0 + expect(tokenDetails.expires).to(beCloseTo(NSDate().dateByAddingTimeInterval(ARTDefault.ttl()), within: 1.0)) + expect(serverTimeRequestCount) == 2 done() } } - - //set fake offset to check it affects token request `timestamp` property - let fakeOffset = NSNumber(integer: 20000) //20 seconds - rest.auth.timeOffset = fakeOffset - - var secondTokenRequestTimeStamp: NSDate? + } + + it("should use the local clock offset to calculate the server time") { + let options = AblyTests.commonAppSetup() + let rest = ARTRest(options: options) + + let authOptions = ARTAuthOptions() + authOptions.key = options.key + authOptions.queryTime = false + + let fakeOffset: NSTimeInterval = 60 //1 minute + rest.auth.setTimeOffset(fakeOffset) + waitUntil(timeout: testTimeout) { done in - rest.auth.createTokenRequest(nil, options: authOptions, callback: { tokenRequest, error in + rest.auth.createTokenRequest(nil, options: authOptions) { tokenRequest, error in expect(error).to(beNil()) guard let tokenRequest = tokenRequest else { - XCTFail("tokenRequest is nil"); done(); return + fail("TokenRequest is nil"); done(); return } - secondTokenRequestTimeStamp = tokenRequest.timestamp - - let firstMillis = firstTokenRequestTimeStamp!.timeIntervalSince1970 * 1000 - let secondMillis = secondTokenRequestTimeStamp!.timeIntervalSince1970 * 1000 - let diff = secondMillis - firstMillis - expect(CLongLong(diff) >= fakeOffset.longLongValue).to(beTrue()) + expect(rest.auth.timeOffset) == fakeOffset + expect(tokenRequest.timestamp).to(beCloseTo(NSDate().dateByAddingTimeInterval(fakeOffset), within: 0.5)) done() - }) + } + } + } + + it("should request server time when queryTime is true even if the time offset is assigned") { + let options = AblyTests.commonAppSetup() + let rest = ARTRest(options: options) + + var serverTimeRequestCount = 0 + let hook = rest.testSuite_injectIntoMethodAfter(#selector(rest.time)) { + serverTimeRequestCount += 1 + } + defer { hook.remove() } + + let fakeOffset: NSTimeInterval = 60 //1 minute + rest.auth.setTimeOffset(fakeOffset) + + let authOptions = ARTAuthOptions() + authOptions.key = options.key + authOptions.queryTime = true + + waitUntil(timeout: testTimeout) { done in + expect(rest.auth.timeOffset).to(equal(fakeOffset)) + rest.auth.authorise(nil, options: authOptions) { tokenDetails, error in + expect(error).to(beNil()) + expect(tokenDetails).toNot(beNil()) + expect(rest.auth.timeOffset).toNot(equal(fakeOffset)) + expect(serverTimeRequestCount) == 1 + done() + } } } + } } From a6e952c8d4551b7bd838956c099498c6848211ab Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Tue, 4 Oct 2016 20:26:49 +0100 Subject: [PATCH 4/7] RSA10k: discard the time offset from system clock notifications --- Source/ARTAuth.m | 33 +++++++++++++++++++++++++++++++++ Spec/Auth.swift | 20 ++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/Source/ARTAuth.m b/Source/ARTAuth.m index 5e8ef9146..347b6ebb0 100644 --- a/Source/ARTAuth.m +++ b/Source/ARTAuth.m @@ -8,6 +8,10 @@ #import "ARTAuth+Private.h" +#ifdef TARGET_OS_IPHONE +#import +#endif + #import "ARTRest.h" #import "ARTRest+Private.h" #import "ARTHttp.h" @@ -36,11 +40,40 @@ - (instancetype)init:(ARTRest *)rest withOptions:(ARTClientOptions *)options { _protocolClientId = nil; _tokenParams = options.defaultTokenParams ? : [[ARTTokenParams alloc] initWithOptions:self.options]; [self validate:options]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(didReceiveCurrentLocaleDidChangeNotification:) + name:NSCurrentLocaleDidChangeNotification + object:nil]; + + #ifdef TARGET_OS_IPHONE + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(didReceiveApplicationSignificantTimeChangeNotification:) + name:UIApplicationSignificantTimeChangeNotification + object:nil]; + #endif } return self; } +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:NSCurrentLocaleDidChangeNotification object:nil]; + #ifdef TARGET_OS_IPHONE + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationSignificantTimeChangeNotification object:nil]; + #endif +} + +- (void)didReceiveCurrentLocaleDidChangeNotification:(NSNotification *)notification { + [self.logger debug:__FILE__ line:__LINE__ message:@"RS:%p NSCurrentLocaleDidChangeNotification received", _rest]; + [self discardTimeOffset]; +} + +- (void)didReceiveApplicationSignificantTimeChangeNotification:(NSNotification *)notification { + [self.logger debug:__FILE__ line:__LINE__ message:@"RS:%p UIApplicationSignificantTimeChangeNotification received", _rest]; + [self discardTimeOffset]; +} + - (void)validate:(ARTClientOptions *)options { [self.logger debug:__FILE__ line:__LINE__ message:@"RS:%p validating %@", _rest, options]; if ([options isBasicAuth]) { diff --git a/Spec/Auth.swift b/Spec/Auth.swift index b551d8c2c..d42244d51 100644 --- a/Spec/Auth.swift +++ b/Spec/Auth.swift @@ -2600,6 +2600,26 @@ class Auth : QuickSpec { } } + it("should discard the time offset in situations in which it may have been invalidated") { + let rest = ARTRest(options: AblyTests.commonAppSetup()) + + var discardTimeOffsetCallCount = 0 + let hook = rest.auth.testSuite_injectIntoMethodAfter(#selector(rest.auth.discardTimeOffset)) { + discardTimeOffsetCallCount += 1 + } + defer { hook.remove() } + + // Force notification + NSNotificationCenter.defaultCenter().postNotificationName(UIApplicationSignificantTimeChangeNotification, object: nil) + + expect(discardTimeOffsetCallCount).toEventually(equal(1), timeout: testTimeout) + + // Force notification + NSNotificationCenter.defaultCenter().postNotificationName(NSCurrentLocaleDidChangeNotification, object: nil) + + expect(discardTimeOffsetCallCount).toEventually(equal(2), timeout: testTimeout) + } + } } From 04c6e1454b2208775fb5f07ff7b9eb7991a8ea3b Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 5 Oct 2016 11:38:23 +0100 Subject: [PATCH 5/7] Test suite: mock server date --- Spec/Auth.swift | 99 +++++++++++++++---------------------------------- 1 file changed, 29 insertions(+), 70 deletions(-) diff --git a/Spec/Auth.swift b/Spec/Auth.swift index d42244d51..5044700de 100644 --- a/Spec/Auth.swift +++ b/Spec/Auth.swift @@ -1209,17 +1209,14 @@ class Auth : QuickSpec { authOptions.queryTime = true authOptions.key = options.key - var serverDate = NSDate() - waitUntil(timeout: testTimeout) { done in - rest.time { date, error in - expect(error).to(beNil()) - guard let date = date else { - XCTFail("No server time"); done(); return - } - serverDate = date - done() - } + let mockServerDate = NSDate().dateByAddingTimeInterval(120) + rest.auth.testSuite_returnValueFor(NSSelectorFromString("handleServerTime:"), withDate: mockServerDate) + + var serverTimeRequestCount = 0 + let hook = rest.testSuite_injectIntoMethodAfter(#selector(rest.time(_:))) { + serverTimeRequestCount += 1 } + defer { hook.remove() } waitUntil(timeout: testTimeout) { done in rest.auth.createTokenRequest(tokenParams, options: authOptions) { tokenRequest, error in @@ -1228,7 +1225,8 @@ class Auth : QuickSpec { XCTFail("tokenRequest is nil"); done(); return } expect(tokenRequest.clientId).to(beNil()) - expect(tokenRequest.timestamp).to(beCloseTo(serverDate, within: 1.0)) //1 Second + expect(tokenRequest.timestamp).to(beCloseTo(mockServerDate)) + expect(serverTimeRequestCount) == 1 expect(tokenRequest.ttl).to(equal(ExpectedTokenParams.ttl)) expect(tokenRequest.capability).to(equal(ExpectedTokenParams.capability)) done() @@ -2387,14 +2385,9 @@ class Auth : QuickSpec { let options = AblyTests.commonAppSetup() let rest = ARTRest(options: options) - var serverDate: NSDate? - waitUntil(timeout: testTimeout) { done in - rest.time { date, error in - expect(error).to(beNil()) - serverDate = date - done() - } - } + let mockServerDate = NSDate().dateByAddingTimeInterval(120) + rest.auth.testSuite_returnValueFor(NSSelectorFromString("handleServerTime:"), withDate: mockServerDate) + let currentDate = NSDate() var serverTimeRequestCount = 0 let hook = rest.testSuite_injectIntoMethodAfter(#selector(rest.time(_:))) { @@ -2412,13 +2405,9 @@ class Auth : QuickSpec { guard let tokenDetails = tokenDetails else { fail("TokenDetails is nil"); done(); return } - guard let serverDate = serverDate else { - fail("ServerDate is nil"); done(); return - } - expect(rest.auth.timeOffset).toNot(beCloseTo(0)) - expect(tokenDetails.issued).to(beCloseTo(NSDate().dateByAddingTimeInterval(rest.auth.timeOffset), within: 1.0)) - expect(tokenDetails.issued).to(beCloseTo(serverDate, within: 1.0)) - expect(tokenDetails.expires).to(beCloseTo(serverDate.dateByAddingTimeInterval(ARTDefault.ttl()), within: 1.0)) + expect(rest.auth.timeOffset).toNot(equal(0)) + let calculatedServerDate = currentDate.dateByAddingTimeInterval(rest.auth.timeOffset) + expect(calculatedServerDate).to(beCloseTo(mockServerDate, within: 0.5)) expect(serverTimeRequestCount) == 1 done() }) @@ -2432,11 +2421,10 @@ class Auth : QuickSpec { guard let tokenDetails = tokenDetails else { fail("TokenDetails is nil"); done(); return } - expect(rest.auth.timeOffset).toNot(beCloseTo(0)) - expect(tokenDetails.issued).to(beCloseTo(NSDate().dateByAddingTimeInterval(rest.auth.timeOffset), within: 1.0)) - let assumedServerDate = NSDate().dateByAddingTimeInterval(rest.auth.timeOffset) - expect(tokenDetails.expires).to(beCloseTo(assumedServerDate.dateByAddingTimeInterval(ARTDefault.ttl()), within: 1.0)) - expect(serverTimeRequestCount) == 2 + expect(rest.auth.timeOffset).toNot(equal(0)) + let calculatedServerDate = currentDate.dateByAddingTimeInterval(rest.auth.timeOffset) + expect(calculatedServerDate).to(beCloseTo(mockServerDate, within: 0.5)) + expect(serverTimeRequestCount) == 1 done() } } @@ -2446,14 +2434,8 @@ class Auth : QuickSpec { let options = AblyTests.commonAppSetup() let rest = ARTRest(options: options) - var serverDate: NSDate? - waitUntil(timeout: testTimeout) { done in - rest.time { date, error in - expect(error).to(beNil()) - serverDate = date - done() - } - } + let mockServerDate = NSDate().dateByAddingTimeInterval(120) + rest.auth.testSuite_returnValueFor(NSSelectorFromString("handleServerTime:"), withDate: mockServerDate) var serverTimeRequestCount = 0 let hook = rest.testSuite_injectIntoMethodAfter(#selector(rest.time(_:))) { @@ -2471,12 +2453,9 @@ class Auth : QuickSpec { guard let tokenRequest = tokenRequest else { fail("TokenRequest is nil"); done(); return } - guard let serverDate = serverDate else { - fail("ServerDate is nil"); done(); return - } - expect(rest.auth.timeOffset).toNot(beCloseTo(0)) - expect(tokenRequest.timestamp).to(beCloseTo(NSDate().dateByAddingTimeInterval(rest.auth.timeOffset), within: 0.1)) - expect(tokenRequest.timestamp).to(beCloseTo(serverDate, within: 0.5)) + expect(rest.auth.timeOffset).toNot(equal(0)) + expect(mockServerDate.timeIntervalSinceNow).to(beCloseTo(rest.auth.timeOffset, within: 0.1)) + expect(tokenRequest.timestamp).to(beCloseTo(mockServerDate)) expect(serverTimeRequestCount) == 1 done() } @@ -2501,8 +2480,8 @@ class Auth : QuickSpec { fail("TokenDetails is nil"); done(); return } expect(rest.auth.timeOffset).toNot(beCloseTo(0)) - let assumedServerDate = NSDate().dateByAddingTimeInterval(rest.auth.timeOffset) - expect(tokenDetails.expires).to(beCloseTo(assumedServerDate.dateByAddingTimeInterval(ARTDefault.ttl()), within: 1.0)) + let calculatedServerDate = NSDate().dateByAddingTimeInterval(rest.auth.timeOffset) + expect(tokenDetails.expires).to(beCloseTo(calculatedServerDate.dateByAddingTimeInterval(ARTDefault.ttl()), within: 1.0)) expect(serverTimeRequestCount) == 1 done() } @@ -2515,33 +2494,12 @@ class Auth : QuickSpec { waitUntil(timeout: testTimeout) { done in rest.auth.authorise(nil, options: nil) { tokenDetails, error in - expect(error).to(beNil()) - guard let tokenDetails = tokenDetails else { - fail("TokenDetails is nil"); done(); return - } - expect(rest.auth.timeOffset).toNot(beCloseTo(0)) - let assumedServerDate = NSDate().dateByAddingTimeInterval(rest.auth.timeOffset) - expect(tokenDetails.expires).to(beCloseTo(assumedServerDate.dateByAddingTimeInterval(ARTDefault.ttl()), within: 1.0)) - expect(serverTimeRequestCount) == 2 - done() - } - } - - rest.auth.testSuite_forceTokenToExpire() - - let authOptions = ARTAuthOptions() - authOptions.key = options.key - authOptions.queryTime = false - - waitUntil(timeout: testTimeout) { done in - rest.auth.authorise(nil, options: authOptions) { tokenDetails, error in expect(error).to(beNil()) guard let tokenDetails = tokenDetails else { fail("TokenDetails is nil"); done(); return } expect(rest.auth.timeOffset) == 0 - expect(tokenDetails.expires).to(beCloseTo(NSDate().dateByAddingTimeInterval(ARTDefault.ttl()), within: 1.0)) - expect(serverTimeRequestCount) == 2 + expect(serverTimeRequestCount) == 1 done() } } @@ -2565,7 +2523,8 @@ class Auth : QuickSpec { fail("TokenRequest is nil"); done(); return } expect(rest.auth.timeOffset) == fakeOffset - expect(tokenRequest.timestamp).to(beCloseTo(NSDate().dateByAddingTimeInterval(fakeOffset), within: 0.5)) + let calculatedServerDate = NSDate().dateByAddingTimeInterval(rest.auth.timeOffset) + expect(tokenRequest.timestamp).to(beCloseTo(calculatedServerDate, within: 0.5)) done() } } From 66d265587b10eeb4eedad18f5d784083c77cbccf Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Wed, 5 Oct 2016 12:03:02 +0100 Subject: [PATCH 6/7] Travis: fixes scan stable version --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cd12e83df..d20e7a60f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,9 @@ before_install: install: # Install Scan # Automatically switches to the travis formatter when running on Travis - - gem install scan + - gem install scan -v 0.11.3 + - gem uninstall xcpretty -aIx + - gem install xcpretty -v 0.2.2 # Remove old versions of CocoaPods - gem uninstall cocoapods -aIx # Install CocoaPods From e68c8b079be35efa8336bd550a9c9a8564baafbe Mon Sep 17 00:00:00 2001 From: Ricardo Pereira Date: Tue, 11 Oct 2016 00:08:26 +0100 Subject: [PATCH 7/7] Fix testRecoverFails: connect with an invalid connection Key --- Tests/ARTRealtimeRecoverTest.m | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Tests/ARTRealtimeRecoverTest.m b/Tests/ARTRealtimeRecoverTest.m index 164d9b836..abcaf909c 100644 --- a/Tests/ARTRealtimeRecoverTest.m +++ b/Tests/ARTRealtimeRecoverTest.m @@ -86,9 +86,10 @@ - (void)testRecoverFails { [realtime.connection on:^(ARTConnectionStateChange *stateChange) { ARTRealtimeConnectionState cState = stateChange.current; ARTErrorInfo *errorInfo = stateChange.reason; - if (cState == ARTRealtimeFailed) { - // 80018 - Invalid connectionKey: bad_recovery_key - XCTAssertEqual(errorInfo.code, 80018); + // If you connect with an invalid connection Key, then it should connect and get an error saying it could not resume + if (cState == ARTRealtimeConnected) { + // 80008 - Unable to recover connection: not found (bad_recovery_key) + XCTAssertEqual(errorInfo.code, 80008); [expectation fulfill]; } }];