From 2c3cbe3850f9995938be309540e80bf948ffd54f Mon Sep 17 00:00:00 2001 From: Fabio Milano Date: Sun, 5 Jun 2016 23:32:53 +0200 Subject: [PATCH 1/4] Implemented a simple iCloud Key Value Store that conforms to requirements for an OAuth Access Token Store. --- Heimdallr.xcodeproj/project.pbxproj | 6 ++ .../OAuthAccessTokenICloudKeyValueStore.swift | 63 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 Heimdallr/Core/OAuthAccessTokenICloudKeyValueStore.swift diff --git a/Heimdallr.xcodeproj/project.pbxproj b/Heimdallr.xcodeproj/project.pbxproj index e541702..96d9f15 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 */; }; + E5E39D991D04C5CE00713AC9 /* OAuthAccessTokenICloudKeyValueStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E39D981D04C5CE00713AC9 /* OAuthAccessTokenICloudKeyValueStore.swift */; }; + E5E39D9A1D04D09000713AC9 /* OAuthAccessTokenICloudKeyValueStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5E39D981D04C5CE00713AC9 /* OAuthAccessTokenICloudKeyValueStore.swift */; }; /* 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 = ""; }; + E5E39D981D04C5CE00713AC9 /* OAuthAccessTokenICloudKeyValueStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OAuthAccessTokenICloudKeyValueStore.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -366,6 +369,7 @@ DC5220511BEA320B00F37F2A /* OAuthAuthorizationGrant.swift */, DC5220521BEA320B00F37F2A /* OAuthClientCredentials.swift */, DC5220531BEA320B00F37F2A /* OAuthError.swift */, + E5E39D981D04C5CE00713AC9 /* OAuthAccessTokenICloudKeyValueStore.swift */, ); path = Core; sourceTree = ""; @@ -892,6 +896,7 @@ DC5220701BEA321300F37F2A /* HeimdallrHTTPClientNSURLSession.swift in Sources */, DC5220751BEA321300F37F2A /* OAuthAccessToken.swift in Sources */, DC5220721BEA321300F37F2A /* HeimdallrResourceRequestAuthenticatorHTTPAuthorizationHeader.swift in Sources */, + E5E39D9A1D04D09000713AC9 /* OAuthAccessTokenICloudKeyValueStore.swift in Sources */, DC52206F1BEA321300F37F2A /* HeimdallrHTTPClient.swift in Sources */, E258A1691CC6BC7B00649F5A /* OAuthAccessTokenParser.swift in Sources */, E258A16C1CC6BC8600649F5A /* OAuthAccessTokenDefaultParser.swift in Sources */, @@ -927,6 +932,7 @@ DC52207D1BEA321500F37F2A /* HeimdallrHTTPClientNSURLSession.swift in Sources */, DC5220821BEA321500F37F2A /* OAuthAccessToken.swift in Sources */, DC52207F1BEA321500F37F2A /* HeimdallrResourceRequestAuthenticatorHTTPAuthorizationHeader.swift in Sources */, + E5E39D991D04C5CE00713AC9 /* OAuthAccessTokenICloudKeyValueStore.swift in Sources */, E258A1681CC6B8C500649F5A /* OAuthAccessTokenParser.swift in Sources */, DC52207C1BEA321500F37F2A /* HeimdallrHTTPClient.swift in Sources */, E258A1661CC6B66900649F5A /* OAuthAccessTokenDefaultParser.swift in Sources */, diff --git a/Heimdallr/Core/OAuthAccessTokenICloudKeyValueStore.swift b/Heimdallr/Core/OAuthAccessTokenICloudKeyValueStore.swift new file mode 100644 index 0000000..f9b6773 --- /dev/null +++ b/Heimdallr/Core/OAuthAccessTokenICloudKeyValueStore.swift @@ -0,0 +1,63 @@ +// +// OAuthAccessTokenICloudKeyValueStore.swift +// Heimdallr +// +// Created by Fabio Milano on 05/06/16. +// Copyright © 2016 B264 GmbH. All rights reserved. +// + +import Foundation + +/// A persistent iCloud Key Value based access token store. +@objc public class OAuthAccessTokenICloudKeyValueStore: NSObject, OAuthAccessTokenStore { + private let store = NSUbiquitousKeyValueStore.defaultStore() + private let service: String + + public init(service: String = "de.rheinfabrik.heimdallr.oauth") { + self.service = service + } + + public func storeAccessToken(accessToken: OAuthAccessToken?) { + if let accessToken = accessToken { + var accessTokenDictionaryRepresentation = [String: String]() + + accessTokenDictionaryRepresentation.updateValue(accessToken.accessToken, forKey: "access_token") + accessTokenDictionaryRepresentation.updateValue(accessToken.tokenType, forKey: "token_type") + + if let refreshToken = accessToken.refreshToken { + accessTokenDictionaryRepresentation.updateValue(refreshToken, forKey: "refresh_token") + } + + accessTokenDictionaryRepresentation.updateValue(accessToken.accessToken, forKey: "access_token") + + if let expiresAt = accessToken.expiresAt { + accessTokenDictionaryRepresentation.updateValue(expiresAt.timeIntervalSince1970.description, forKey: "expires_at") + } + + store.setDictionary(accessTokenDictionaryRepresentation, forKey: service) + } + + store.synchronize() + } + + public func retrieveAccessToken() -> OAuthAccessToken? { + if let accessTokenDictionaryRepresentation = store.dictionaryForKey(service) { + let accessToken = accessTokenDictionaryRepresentation["access_token"] as? String + let tokenType = accessTokenDictionaryRepresentation["token_type"] as? String + let refreshToken = accessTokenDictionaryRepresentation["refresh_token"] as? String + let expiresAtString = accessTokenDictionaryRepresentation["expires_at"] as? String + + let expiresAt = expiresAtString.flatMap { description in + return Double(description).flatMap { expiresAtInSeconds in + return NSDate(timeIntervalSince1970: expiresAtInSeconds) + } + } + + if let accessToken = accessToken, tokenType = tokenType { + return OAuthAccessToken(accessToken: accessToken, tokenType: tokenType, expiresAt: expiresAt, refreshToken: refreshToken) + } + } + + return nil + } +} \ No newline at end of file From 7a56746e62d27b48f2943577360a6c16967d495a Mon Sep 17 00:00:00 2001 From: Fabio Milano Date: Mon, 6 Jun 2016 00:01:38 +0200 Subject: [PATCH 2/4] Implemented private method for synchronising an iCloud key-value storage. If in DEBUG configuration a proper exception is raised in case the synchronisation fails. --- .../OAuthAccessTokenICloudKeyValueStore.swift | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Heimdallr/Core/OAuthAccessTokenICloudKeyValueStore.swift b/Heimdallr/Core/OAuthAccessTokenICloudKeyValueStore.swift index f9b6773..0fbfcf2 100644 --- a/Heimdallr/Core/OAuthAccessTokenICloudKeyValueStore.swift +++ b/Heimdallr/Core/OAuthAccessTokenICloudKeyValueStore.swift @@ -37,10 +37,12 @@ import Foundation store.setDictionary(accessTokenDictionaryRepresentation, forKey: service) } - store.synchronize() + synchronize() } public func retrieveAccessToken() -> OAuthAccessToken? { + synchronize() + if let accessTokenDictionaryRepresentation = store.dictionaryForKey(service) { let accessToken = accessTokenDictionaryRepresentation["access_token"] as? String let tokenType = accessTokenDictionaryRepresentation["token_type"] as? String @@ -60,4 +62,21 @@ import Foundation return nil } + + private func synchronize() -> Void { + #if DEBUG + let synchronized = store.synchronize() + + let userInfo = [ + NSLocalizedDescriptionKey: NSLocalizedString("Could not initialize an iCloud Key Value Store", comment: ""), + NSLocalizedFailureReasonErrorKey: NSLocalizedString("Something went wrong in initializing an iCloud Key Value Store.", comment: "") + ] + + if !synchronized { + NSException(name: "OAuthAccessTokenICloudKeyValueStore", reason: NSLocalizedString("Make sure the app has registered to proper iCloud capabilities.", comment: ""), userInfo: userInfo).raise() + } + #else + store.synchronize() + #endif + } } \ No newline at end of file From 528af1dea81450a748a34390900313278d364172 Mon Sep 17 00:00:00 2001 From: Fabio Milano Date: Mon, 6 Jun 2016 00:02:44 +0200 Subject: [PATCH 3/4] Updated docs --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a3a2495..16c8392 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,12 @@ protocol OAuthAccessTokenStore { } ``` -Heimdallr ships with an already built-in persistent keychain-based access token store. The service is configurable: +Heimdallr ships with two already built-in persistent access token store. + + +#### Keychain-based access token store + +The service is configurable: ```swift var service: String! @@ -115,6 +120,12 @@ var service: String! let accessTokenStore = OAuthAccessTokenKeychainStore(service: service) ``` +#### iCloud Key Value-based access token store + +It stores values on the iCloud Key-Value Storage container for the current iCloud account. + +Before using this persistent access token store, please make sure your are app has been properly setup for __iCloud Key-Value Storage__ capability. More info [here](https://developer.apple.com/library/mac/documentation/IDEs/Conceptual/AppDistributionGuide/AddingCapabilities/AddingCapabilities.html#//apple_ref/doc/uid/TP40012582-CH26-SW19). + ### HeimdallrHTTPClient An HTTP client that can be used by Heimdallr for requesting access tokens. It must implement the following `sendRequest` method: From 6c545a2824f8adc6f2c02547e1dc6327cf3436bd Mon Sep 17 00:00:00 2001 From: Fabio Milano Date: Mon, 6 Jun 2016 00:15:27 +0200 Subject: [PATCH 4/4] Improved if statement in case of error when trying to initializing an iCloud Key-Value based access token store. --- .../Core/OAuthAccessTokenICloudKeyValueStore.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Heimdallr/Core/OAuthAccessTokenICloudKeyValueStore.swift b/Heimdallr/Core/OAuthAccessTokenICloudKeyValueStore.swift index 0fbfcf2..62a08c1 100644 --- a/Heimdallr/Core/OAuthAccessTokenICloudKeyValueStore.swift +++ b/Heimdallr/Core/OAuthAccessTokenICloudKeyValueStore.swift @@ -67,12 +67,12 @@ import Foundation #if DEBUG let synchronized = store.synchronize() - let userInfo = [ - NSLocalizedDescriptionKey: NSLocalizedString("Could not initialize an iCloud Key Value Store", comment: ""), - NSLocalizedFailureReasonErrorKey: NSLocalizedString("Something went wrong in initializing an iCloud Key Value Store.", comment: "") - ] - if !synchronized { + let userInfo = [ + NSLocalizedDescriptionKey: NSLocalizedString("Could not initialize an iCloud Key Value Store", comment: ""), + NSLocalizedFailureReasonErrorKey: NSLocalizedString("Something went wrong in initializing an iCloud Key Value Store.", comment: "") + ] + NSException(name: "OAuthAccessTokenICloudKeyValueStore", reason: NSLocalizedString("Make sure the app has registered to proper iCloud capabilities.", comment: ""), userInfo: userInfo).raise() } #else