diff --git a/Heimdallr.xcodeproj/project.pbxproj b/Heimdallr.xcodeproj/project.pbxproj index e541702..56b81d4 100644 --- a/Heimdallr.xcodeproj/project.pbxproj +++ b/Heimdallr.xcodeproj/project.pbxproj @@ -129,6 +129,8 @@ E258A16C1CC6BC8600649F5A /* OAuthAccessTokenDefaultParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258A1651CC6B66900649F5A /* OAuthAccessTokenDefaultParser.swift */; }; E258A16D1CC6BC8700649F5A /* OAuthAccessTokenDefaultParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258A1651CC6B66900649F5A /* OAuthAccessTokenDefaultParser.swift */; }; E258A16E1CC6BC8800649F5A /* OAuthAccessTokenDefaultParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258A1651CC6B66900649F5A /* OAuthAccessTokenDefaultParser.swift */; }; + E2A5A5031CF4D46A0087840B /* request-valid-norefresh.json in Resources */ = {isa = PBXBuildFile; fileRef = E2A5A5011CF4D4620087840B /* request-valid-norefresh.json */; }; + E2A5A5041CF4D46B0087840B /* request-valid-norefresh.json in Resources */ = {isa = PBXBuildFile; fileRef = E2A5A5011CF4D4620087840B /* request-valid-norefresh.json */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -237,6 +239,7 @@ DC712A5E1C85AD77009860A5 /* watchOS-StaticLibrary.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "watchOS-StaticLibrary.xcconfig"; sourceTree = ""; }; E258A1651CC6B66900649F5A /* OAuthAccessTokenDefaultParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OAuthAccessTokenDefaultParser.swift; sourceTree = ""; }; E258A1671CC6B8C500649F5A /* OAuthAccessTokenParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OAuthAccessTokenParser.swift; sourceTree = ""; }; + E2A5A5011CF4D4620087840B /* request-valid-norefresh.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "request-valid-norefresh.json"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -438,6 +441,7 @@ DC5220AD1BEA32A000F37F2A /* authorize-valid.json */, DC5220AE1BEA32A000F37F2A /* request-invalid-norefresh.json */, DC5220AF1BEA32A000F37F2A /* request-invalid.json */, + E2A5A5011CF4D4620087840B /* request-valid-norefresh.json */, DC5220B01BEA32A000F37F2A /* request-valid.json */, ); path = "JSON Responses"; @@ -813,6 +817,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + E2A5A5041CF4D46B0087840B /* request-valid-norefresh.json in Resources */, DC5220BF1BEA32A000F37F2A /* request-valid.json in Resources */, DC5220B11BEA32A000F37F2A /* authorize-error.json in Resources */, DC5220B71BEA32A000F37F2A /* authorize-invalid.json in Resources */, @@ -835,6 +840,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + E2A5A5031CF4D46A0087840B /* request-valid-norefresh.json in Resources */, DC5220C01BEA32A000F37F2A /* request-valid.json in Resources */, DC5220B21BEA32A000F37F2A /* authorize-error.json in Resources */, DC5220B81BEA32A000F37F2A /* authorize-invalid.json in Resources */, diff --git a/Heimdallr/Core/Heimdallr.swift b/Heimdallr/Core/Heimdallr.swift index ef7cddd..5a01001 100644 --- a/Heimdallr/Core/Heimdallr.swift +++ b/Heimdallr/Core/Heimdallr.swift @@ -20,9 +20,6 @@ public let HeimdallrErrorNotAuthorized = 2 get { return accessTokenStore.retrieveAccessToken() } - set { - accessTokenStore.storeAccessToken(newValue) - } } private let accessTokenParser: OAuthAccessTokenParser private let httpClient: HeimdallrHTTPClient @@ -76,7 +73,7 @@ public let HeimdallrErrorNotAuthorized = 2 /// **Note:** Sets the access token's expiration date to /// 1 January 1970, GMT. public func invalidateAccessToken() { - accessToken = accessToken?.copy(expiresAt: NSDate(timeIntervalSince1970: 0)) + accessTokenStore.storeAccessToken(accessToken?.copy(expiresAt: NSDate(timeIntervalSince1970: 0))) } /// Clears the currently stored access token, if any. @@ -143,8 +140,8 @@ public let HeimdallrErrorNotAuthorized = 2 } else if (response as! NSHTTPURLResponse).statusCode == 200 { switch self.accessTokenParser.parse(data!) { case let .Success(accessToken): - self.accessToken = accessToken - completion(.Success(accessToken)) + let updatedAccessToken = self.updateAccessToken(accessToken) + completion(.Success(updatedAccessToken)) default: let userInfo = [ NSLocalizedDescriptionKey: NSLocalizedString("Could not authorize grant", comment: ""), @@ -170,6 +167,24 @@ public let HeimdallrErrorNotAuthorized = 2 } } } + + /// Updates the stored access token with a new one. + /// + /// - parameter accessToken: The new access token. + /// + /// - returns: The updated access token. + private func updateAccessToken(accessToken: OAuthAccessToken) -> OAuthAccessToken { + var updatedAccessToken = accessToken + + if accessToken.refreshToken == nil { + if let storedRefreshToken = self.accessToken?.refreshToken { + updatedAccessToken = accessToken.copy(refreshToken: storedRefreshToken) + } + } + + accessTokenStore.storeAccessToken(updatedAccessToken) + return updatedAccessToken + } /// Alters the given request by adding authentication with an access token. /// diff --git a/HeimdallrTests/Core/HeimdallrSpec.swift b/HeimdallrTests/Core/HeimdallrSpec.swift index f5385b4..ed1bd3d 100644 --- a/HeimdallrTests/Core/HeimdallrSpec.swift +++ b/HeimdallrTests/Core/HeimdallrSpec.swift @@ -667,6 +667,36 @@ class HeimdallrSpec: QuickSpec { } context("when refreshing the access token succeeds") { + beforeEach { + OHHTTPStubs.stubRequestsPassingTest({ request in + return (request.URL!.absoluteString == "http://rheinfabrik.de") + }, withStubResponse: { request in + return OHHTTPStubsResponse(data: NSData(contentsOfFile: self.bundle.pathForResource("request-valid-norefresh", ofType: "json")!)!, statusCode: 200, headers: [ "Content-Type": "application/json" ]) + }) + + waitUntil { done in + heimdallr.authenticateRequest(request) { result = $0; done() } + } + } + + it("succeeds") { + expect(result?.value).toNot(beNil()) + } + + it("attempts to parse the fresh token") { + expect(accessTokenParser.timesCalled).to(equal(2)) + } + + it("authenticates the request using the resource request authenticator") { + expect(result?.value?.valueForHTTPHeaderField("MockAuthorized")).to(equal("totally")) + } + + it("keeps the original refresh token") { + expect(accessTokenStore.retrieveAccessToken()?.refreshToken).to(equal("ZmVhMThjYzUyZDM3MmIzNDcyMDMyMzc2MzhmYTg4YWM0MWYyYmQxZmFlMTE2Mzk0MWY5YTk1YWQ4ZDBmYzIxZA")) + } + } + + context("when refreshing the access token succeeds and overrides the refresh token") { beforeEach { OHHTTPStubs.stubRequestsPassingTest({ request in return (request.URL!.absoluteString == "http://rheinfabrik.de") @@ -690,6 +720,10 @@ class HeimdallrSpec: QuickSpec { it("authenticates the request using the resource request authenticator") { expect(result?.value?.valueForHTTPHeaderField("MockAuthorized")).to(equal("totally")) } + + it("overrides the refresh token") { + expect(accessTokenStore.retrieveAccessToken()?.refreshToken).to(equal("Vs25754VHY5CSovX2GRgLCLbKKZk8BQl7vmAoRav2agcY75uz338NyassLQCM8MeF04clqvtBeXG5o2wiuiTay")) + } } context("when refreshing the access token fails") { diff --git a/HeimdallrTests/Core/JSON Responses/request-valid-norefresh.json b/HeimdallrTests/Core/JSON Responses/request-valid-norefresh.json new file mode 100644 index 0000000..4f2ddc2 --- /dev/null +++ b/HeimdallrTests/Core/JSON Responses/request-valid-norefresh.json @@ -0,0 +1 @@ +{"access_token":"MTQzM2U3YTI3YmQyOWQ5YzQ0NjY4YTZkYjM0MjczYmZhNWI1M2YxM2Y1MjgwYTg3NDk3ZDc4ZGUzM2YxZmJjZQ","expires_in":65536,"token_type":"bearer","scope":"user"} diff --git a/HeimdallrTests/Core/JSON Responses/request-valid.json b/HeimdallrTests/Core/JSON Responses/request-valid.json index e30f229..ce191a8 100644 --- a/HeimdallrTests/Core/JSON Responses/request-valid.json +++ b/HeimdallrTests/Core/JSON Responses/request-valid.json @@ -1 +1 @@ -{"access_token":"MTQzM2U3YTI3YmQyOWQ5YzQ0NjY4YTZkYjM0MjczYmZhNWI1M2YxM2Y1MjgwYTg3NDk3ZDc4ZGUzM2YxZmJjZQ","expires_in":65536,"token_type":"bearer","scope":"user","refresh_token":"ZmVhMThjYzUyZDM3MmIzNDcyMDMyMzc2MzhmYTg4YWM0MWYyYmQxZmFlMTE2Mzk0MWY5YTk1YWQ4ZDBmYzIxZA"} +{"access_token":"MTQzM2U3YTI3YmQyOWQ5YzQ0NjY4YTZkYjM0MjczYmZhNWI1M2YxM2Y1MjgwYTg3NDk3ZDc4ZGUzM2YxZmJjZQ","expires_in":65536,"token_type":"bearer","scope":"user","refresh_token":"Vs25754VHY5CSovX2GRgLCLbKKZk8BQl7vmAoRav2agcY75uz338NyassLQCM8MeF04clqvtBeXG5o2wiuiTay"}