From fcff1a67c2698080e0c90f066f40ba8a018e2bc9 Mon Sep 17 00:00:00 2001 From: Daniel Lazarenko Date: Tue, 7 Mar 2017 16:33:36 +0100 Subject: [PATCH] IDMOB-10 Functionality for Apple account consolidation When moving an app from one Apple account to another it's not possible to keep the keychain data. If the data is not there, a logged in user will not be logged in anymore. To overcome this the move can be planned in 2 steps: 1. In the first step an app is upgraded on the old Apple account with a mode SPiDTokenStorageModeMigratePreITunesAccountMove to export keychain data to NSUserDefaults. 2. The next step is an app upgrade on to the new Apple account with a mode SPiDTokenStorageModeMigratePostITunesAccountMove to import data from NSUserDefaults. Both versions should stay in the store for a while until the most users are able to upgrade and run it. --- SPiDExampleApp/SPiDExampleAppDelegate.m | 3 +- SPiDFacebookApp/SPiDFacebookAppDelegate.m | 3 +- SPiDHybridApp/SPiDHybridAppDelegate.m | 3 +- SPiDNativeApp/SPiDNativeAppDelegate.m | 3 +- SPiDSDK.xcodeproj/project.pbxproj | 24 +++ SPiDSDK/SPiDClient.h | 30 +++- SPiDSDK/SPiDClient.m | 47 +++++- SPiDSDK/SPiDSDK.h | 1 - SPiDSDK/SPiDTokenRequest.m | 11 +- SPiDSDK/SPiDTokenStorage.h | 39 +++++ SPiDSDK/SPiDTokenStorage.m | 154 ++++++++++++++++++ SPiDSDK/SPiDTokenStorageKeychainBackend.h | 13 ++ SPiDSDK/SPiDTokenStorageKeychainBackend.m | 33 ++++ SPiDSDK/SPiDTokenStorageUserDefaultsBackend.h | 13 ++ SPiDSDK/SPiDTokenStorageUserDefaultsBackend.m | 80 +++++++++ 15 files changed, 439 insertions(+), 18 deletions(-) create mode 100644 SPiDSDK/SPiDTokenStorage.h create mode 100644 SPiDSDK/SPiDTokenStorage.m create mode 100644 SPiDSDK/SPiDTokenStorageKeychainBackend.h create mode 100644 SPiDSDK/SPiDTokenStorageKeychainBackend.m create mode 100644 SPiDSDK/SPiDTokenStorageUserDefaultsBackend.h create mode 100644 SPiDSDK/SPiDTokenStorageUserDefaultsBackend.m diff --git a/SPiDExampleApp/SPiDExampleAppDelegate.m b/SPiDExampleApp/SPiDExampleAppDelegate.m index 55cf4c8..08fd824 100644 --- a/SPiDExampleApp/SPiDExampleAppDelegate.m +++ b/SPiDExampleApp/SPiDExampleAppDelegate.m @@ -25,7 +25,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [SPiDClient setClientID:ClientID clientSecret:ClientSecret appURLScheme:AppURLScheme - serverURL:[NSURL URLWithString:ServerURL]]; + serverURL:[NSURL URLWithString:ServerURL] + tokenStorageMode:SPiDTokenStorageModeDefault]; [[SPiDClient sharedInstance] setWebViewInitialHTML:@"Loading SPiD login page"]; [self setUseWebView:YES]; // As default, logout as logged in through webview diff --git a/SPiDFacebookApp/SPiDFacebookAppDelegate.m b/SPiDFacebookApp/SPiDFacebookAppDelegate.m index 6084c08..94c6ea7 100644 --- a/SPiDFacebookApp/SPiDFacebookAppDelegate.m +++ b/SPiDFacebookApp/SPiDFacebookAppDelegate.m @@ -33,7 +33,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [SPiDClient setClientID:ClientID clientSecret:ClientSecret appURLScheme:AppURLScheme - serverURL:[NSURL URLWithString:ServerURL]]; + serverURL:[NSURL URLWithString:ServerURL] + tokenStorageMode:SPiDTokenStorageModeDefault]; [[SPiDClient sharedInstance] setSignSecret:SignSecret]; MainViewController *mainViewController = [[MainViewController alloc] init]; diff --git a/SPiDHybridApp/SPiDHybridAppDelegate.m b/SPiDHybridApp/SPiDHybridAppDelegate.m index 170872e..d546fe7 100644 --- a/SPiDHybridApp/SPiDHybridAppDelegate.m +++ b/SPiDHybridApp/SPiDHybridAppDelegate.m @@ -27,7 +27,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [SPiDClient setClientID:ClientID clientSecret:ClientSecret appURLScheme:AppURLScheme - serverURL:[NSURL URLWithString:ServerURL]]; + serverURL:[NSURL URLWithString:ServerURL] + tokenStorageMode:SPiDTokenStorageModeDefault]; [[SPiDClient sharedInstance] setServerClientID:ServerClientID]; [[SPiDClient sharedInstance] setServerRedirectUri:[NSURL URLWithString:ServerRedirectURI]]; diff --git a/SPiDNativeApp/SPiDNativeAppDelegate.m b/SPiDNativeApp/SPiDNativeAppDelegate.m index f917b8d..b5a6c72 100644 --- a/SPiDNativeApp/SPiDNativeAppDelegate.m +++ b/SPiDNativeApp/SPiDNativeAppDelegate.m @@ -27,7 +27,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [SPiDClient setClientID:ClientID clientSecret:ClientSecret appURLScheme:AppURLScheme - serverURL:[NSURL URLWithString:ServerURL]]; + serverURL:[NSURL URLWithString:ServerURL] + tokenStorageMode:SPiDTokenStorageModeDefault]; MainViewController *mainViewController = [[MainViewController alloc] init]; self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; diff --git a/SPiDSDK.xcodeproj/project.pbxproj b/SPiDSDK.xcodeproj/project.pbxproj index 6291b3b..50deefe 100644 --- a/SPiDSDK.xcodeproj/project.pbxproj +++ b/SPiDSDK.xcodeproj/project.pbxproj @@ -21,6 +21,12 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 089318561E6EFAD6002CD789 /* SPiDTokenStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = 089318541E6EFAD6002CD789 /* SPiDTokenStorage.h */; }; + 089318571E6EFAD6002CD789 /* SPiDTokenStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 089318551E6EFAD6002CD789 /* SPiDTokenStorage.m */; }; + 0893185A1E6EFD52002CD789 /* SPiDTokenStorageUserDefaultsBackend.h in Headers */ = {isa = PBXBuildFile; fileRef = 089318581E6EFD52002CD789 /* SPiDTokenStorageUserDefaultsBackend.h */; }; + 0893185B1E6EFD52002CD789 /* SPiDTokenStorageUserDefaultsBackend.m in Sources */ = {isa = PBXBuildFile; fileRef = 089318591E6EFD52002CD789 /* SPiDTokenStorageUserDefaultsBackend.m */; }; + 0893185E1E6EFEA9002CD789 /* SPiDTokenStorageKeychainBackend.h in Headers */ = {isa = PBXBuildFile; fileRef = 0893185C1E6EFEA9002CD789 /* SPiDTokenStorageKeychainBackend.h */; }; + 0893185F1E6EFEA9002CD789 /* SPiDTokenStorageKeychainBackend.m in Sources */ = {isa = PBXBuildFile; fileRef = 0893185D1E6EFEA9002CD789 /* SPiDTokenStorageKeychainBackend.m */; }; 5306208B16C12440001B2A08 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E304ECEC953C2E9C143FD9E2 /* Security.framework */; }; 5306209716C1375D001B2A08 /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5306209616C1375D001B2A08 /* Social.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 5306209B16C1376F001B2A08 /* Accounts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5306209A16C1376F001B2A08 /* Accounts.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; @@ -153,6 +159,12 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 089318541E6EFAD6002CD789 /* SPiDTokenStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPiDTokenStorage.h; sourceTree = ""; }; + 089318551E6EFAD6002CD789 /* SPiDTokenStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPiDTokenStorage.m; sourceTree = ""; }; + 089318581E6EFD52002CD789 /* SPiDTokenStorageUserDefaultsBackend.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPiDTokenStorageUserDefaultsBackend.h; sourceTree = ""; }; + 089318591E6EFD52002CD789 /* SPiDTokenStorageUserDefaultsBackend.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPiDTokenStorageUserDefaultsBackend.m; sourceTree = ""; }; + 0893185C1E6EFEA9002CD789 /* SPiDTokenStorageKeychainBackend.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPiDTokenStorageKeychainBackend.h; sourceTree = ""; }; + 0893185D1E6EFEA9002CD789 /* SPiDTokenStorageKeychainBackend.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPiDTokenStorageKeychainBackend.m; sourceTree = ""; }; 5306208716C082B8001B2A08 /* MainViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainViewController.h; sourceTree = ""; }; 5306209616C1375D001B2A08 /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; }; 5306209816C13764001B2A08 /* AdSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdSupport.framework; path = System/Library/Frameworks/AdSupport.framework; sourceTree = SDKROOT; }; @@ -637,6 +649,12 @@ DAFD374A1CD9F9B300BF0DE3 /* Info.plist */, 9665D1F31E0820C300759F60 /* SPiDAgreements.h */, 9665D1F41E0820C300759F60 /* SPiDAgreements.m */, + 089318541E6EFAD6002CD789 /* SPiDTokenStorage.h */, + 089318551E6EFAD6002CD789 /* SPiDTokenStorage.m */, + 089318581E6EFD52002CD789 /* SPiDTokenStorageUserDefaultsBackend.h */, + 089318591E6EFD52002CD789 /* SPiDTokenStorageUserDefaultsBackend.m */, + 0893185C1E6EFEA9002CD789 /* SPiDTokenStorageKeychainBackend.h */, + 0893185D1E6EFEA9002CD789 /* SPiDTokenStorageKeychainBackend.m */, ); path = SPiDSDK; sourceTree = ""; @@ -648,6 +666,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 0893185A1E6EFD52002CD789 /* SPiDTokenStorageUserDefaultsBackend.h in Headers */, DAFD37551CD9F9D700BF0DE3 /* SPiDResponse.h in Headers */, DAFD37611CD9F9D700BF0DE3 /* SPiDJwt.h in Headers */, DAFD375A1CD9F9D700BF0DE3 /* SPiDClient.h in Headers */, @@ -655,6 +674,7 @@ DAFD374E1CD9F9D700BF0DE3 /* NSError+SPiD.h in Headers */, DAF1DE401CDC78B8007B15B3 /* SPiDWebView.h in Headers */, DAFD37561CD9F9D700BF0DE3 /* SPiDRequest.h in Headers */, + 0893185E1E6EFEA9002CD789 /* SPiDTokenStorageKeychainBackend.h in Headers */, DAFD37491CD9F9B300BF0DE3 /* SPiDSDK.h in Headers */, DAFD37521CD9F9D700BF0DE3 /* SPiDAccessToken.h in Headers */, DAFD37651CD9F9D700BF0DE3 /* SPiDUser.h in Headers */, @@ -665,6 +685,7 @@ DAFD376A1CD9F9D700BF0DE3 /* NSURLRequest+SPiD.h in Headers */, DAFD37631CD9F9D700BF0DE3 /* NSString+Crypto.h in Headers */, DAC183B71CDA161600D08ABD /* NSCharacterSet+SPiD.h in Headers */, + 089318561E6EFAD6002CD789 /* SPiDTokenStorage.h in Headers */, DAFD375D1CD9F9D700BF0DE3 /* NSData+Base64.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -980,12 +1001,15 @@ files = ( DAFD376B1CD9F9D700BF0DE3 /* NSURLRequest+SPiD.m in Sources */, DAFD375B1CD9F9D700BF0DE3 /* SPiDClient.m in Sources */, + 0893185F1E6EFEA9002CD789 /* SPiDTokenStorageKeychainBackend.m in Sources */, DAFD37571CD9F9D700BF0DE3 /* SPiDRequest.m in Sources */, DAFD37541CD9F9D700BF0DE3 /* SPiDResponse.m in Sources */, DAFD37601CD9F9D700BF0DE3 /* SPiDJwt.m in Sources */, + 0893185B1E6EFD52002CD789 /* SPiDTokenStorageUserDefaultsBackend.m in Sources */, DAC183B91CDA161600D08ABD /* NSCharacterSet+SPiD.m in Sources */, DAFD375E1CD9F9D700BF0DE3 /* SPiDTokenRequest.m in Sources */, DAFD37621CD9F9D700BF0DE3 /* NSString+Crypto.m in Sources */, + 089318571E6EFAD6002CD789 /* SPiDTokenStorage.m in Sources */, 9665D1F61E0820C300759F60 /* SPiDAgreements.m in Sources */, DAFD374F1CD9F9D700BF0DE3 /* NSError+SPiD.m in Sources */, DAFD37531CD9F9D700BF0DE3 /* SPiDAccessToken.m in Sources */, diff --git a/SPiDSDK/SPiDClient.h b/SPiDSDK/SPiDClient.h index c248cfe..3c1843b 100644 --- a/SPiDSDK/SPiDClient.h +++ b/SPiDSDK/SPiDClient.h @@ -21,6 +21,32 @@ NS_ASSUME_NONNULL_BEGIN static NSString *const defaultAPIVersionSPiD = @"2"; static NSString *const AccessTokenKeychainIdentification = @"AccessToken"; +/** + Options for persisting user tokens. + + Normally an app should only use the keychain to store the tokens. + During app iTunes account migration the options here let + exporting or importing the data from NSUserDefaults. + This is needed to keep a user logged in after migration. + + The migration can be done in 2 steps (2 app updates): + 1. The first update is published to the old account with SPiDTokenStorageModeMigratePreITunesAccountMove + (and starts writing tokens to NSUserDefaults) + 2. The second update is published to the new account with SPiDTokenStorageModeMigratePostITunesAccountMove + (and starts reading tokens from NSUserDefaults) + Both versions should stay in the store for a while + until the most users are able to upgrade and run it. + Eventually a subsequent update can revert back to use SPiDTokenStorageModeDefault + */ +typedef NS_ENUM(NSUInteger, SPiDTokenStorageMode) { + /** Use only keychain to store and retrieve user tokens */ + SPiDTokenStorageModeDefault, + /** Retrieve user tokens from keychain, store to both keychain and NSUserDefaults */ + SPiDTokenStorageModeMigratePreITunesAccountMove, + /** Try retrieving user tokens from NSUserDefaults after keychain, store to keychain only */ + SPiDTokenStorageModeMigratePostITunesAccountMove, +}; + // debug print used by SPiDSDK #ifdef DEBUG # define SPiDDebugLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); @@ -148,11 +174,13 @@ static NSString *const AccessTokenKeychainIdentification = @"AccessToken"; @param clientSecret The client secret provided by SPiD @param appURLSchema The url schema for the app (eg spidtest://) @param serverURL The url to SPiD + @param tokenStorageMode See SPiDTokenStorageMode */ + (void)setClientID:(NSString *)clientID clientSecret:(NSString *)clientSecret appURLScheme:(NSString *)appURLSchema - serverURL:(NSURL *)serverURL; + serverURL:(NSURL *)serverURL + tokenStorageMode:(SPiDTokenStorageMode)tokenStorageMode; /** Redirects to safari for authorization diff --git a/SPiDSDK/SPiDClient.m b/SPiDSDK/SPiDClient.m index 7a8b08b..687e708 100644 --- a/SPiDSDK/SPiDClient.m +++ b/SPiDSDK/SPiDClient.m @@ -7,10 +7,11 @@ #import "SPiDClient.h" #import "SPiDRequest.h" -#import "SPiDKeychainWrapper.h" +#import "SPiDAccessToken.h" #import "SPiDResponse.h" #import "NSError+SPiD.h" #import "SPiDTokenRequest.h" +#import "SPiDTokenStorage.h" #import "SPiDStatus.h" #import "NSData+Base64.h" #import "SPiDAgreements.h" @@ -42,6 +43,7 @@ - (BOOL)doHandleOpenURL:(NSURL *)url; @property (nonatomic, strong, readwrite) NSMutableArray *waitingRequests; @property (nonatomic, strong) SPiDRequest *authorizationRequest; @property (nonatomic, copy) void (^completionHandler)(NSError *error); +@property (nonatomic, strong) SPiDTokenStorage *tokenStorage; @end @@ -57,7 +59,7 @@ + (SPiDClient *)sharedInstance { NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSStringFromClass([self class]), - NSStringFromSelector(@selector(setClientID:clientSecret:appURLScheme:serverURL:))]; + NSStringFromSelector(@selector(setClientID:clientSecret:appURLScheme:serverURL:tokenStorageMode:))]; } return sharedSPiDClientInstance; @@ -66,7 +68,9 @@ + (SPiDClient *)sharedInstance { + (void)setClientID:(NSString *)clientID clientSecret:(NSString *)clientSecret appURLScheme:(NSString *)appURLSchema - serverURL:(NSURL *)serverURL { + serverURL:(NSURL *)serverURL + tokenStorageMode:(SPiDTokenStorageMode)tokenStorageMode +{ if (sharedSPiDClientInstance != nil) { [NSException raise:NSInternalInconsistencyException @@ -76,7 +80,8 @@ + (void)setClientID:(NSString *)clientID } static dispatch_once_t predicate; dispatch_once(&predicate, ^{ - sharedSPiDClientInstance = [[self alloc] init]; + SPiDTokenStorage *tokenStorage = [self createTokenStorageInMode:tokenStorageMode]; + sharedSPiDClientInstance = [[self alloc] initWithTokenStorage:tokenStorage]; }); [sharedSPiDClientInstance setClientID:clientID]; @@ -130,6 +135,21 @@ + (void)setClientID:(NSString *)clientID [SPiDStatus runStatusRequest]; } ++ (SPiDTokenStorage *)createTokenStorageInMode:(SPiDTokenStorageMode)mode +{ + switch (mode) { + case SPiDTokenStorageModeDefault: + return [[SPiDTokenStorage alloc] initWithReadBackendTypes:@[ @(SPiDTokenStorageBackendTypeKeychain) ] + writeBackendTypes:@[ @(SPiDTokenStorageBackendTypeKeychain) ]]; + case SPiDTokenStorageModeMigratePreITunesAccountMove: + return [[SPiDTokenStorage alloc] initWithReadBackendTypes:@[ @(SPiDTokenStorageBackendTypeKeychain) ] + writeBackendTypes:@[ @(SPiDTokenStorageBackendTypeKeychain), @(SPiDTokenStorageBackendTypeUserDefaults) ]]; + case SPiDTokenStorageModeMigratePostITunesAccountMove: + return [[SPiDTokenStorage alloc] initWithReadBackendTypes:@[ @(SPiDTokenStorageBackendTypeKeychain), @(SPiDTokenStorageBackendTypeUserDefaults) ] + writeBackendTypes:@[ @(SPiDTokenStorageBackendTypeKeychain) ]]; + } +} + - (void)browserRedirectAuthorizationWithCompletionHandler:(void (^)(NSError *response))completionHandler { #if !TARGET_OS_WATCH if (self.accessToken) { // we already have a access token @@ -353,9 +373,11 @@ - (void)emailStatusWithEmail:(NSString *)email completionHandler:(void (^)(SPiDR /// @name Private methods ///--------------------------------------------------------------------------------------- -- (id)init { +- (id)initWithTokenStorage:(SPiDTokenStorage *)tokenStorage +{ if (self = [super init]) { - self.accessToken = [SPiDKeychainWrapper accessTokenFromKeychainForIdentifier:AccessTokenKeychainIdentification]; + self.tokenStorage = tokenStorage; + self.accessToken = [self.tokenStorage loadAccessTokenAndReplicate]; if (![self apiVersionSPiD]) { [self setApiVersionSPiD:[NSString stringWithFormat:@"%@", defaultAPIVersionSPiD]]; } @@ -447,6 +469,15 @@ - (void)refreshAccessTokenAndRerunRequest:(SPiDRequest *)request { } } +- (void)setAndStoreAccessToken:(SPiDAccessToken *)accessToken { + self.accessToken = accessToken; + if (accessToken) { + [self.tokenStorage storeAccessTokenWithValue:accessToken]; + } else { + [self.tokenStorage removeAccessToken]; + } +} + - (void)clearAuthorizationRequest { @synchronized (self.authorizationRequest) { self.authorizationRequest = nil; @@ -468,9 +499,7 @@ - (void)authorizationComplete { - (void)logoutComplete { SPiDDebugLog(@"Logged out from SPiD"); - self.accessToken = nil; - - [SPiDKeychainWrapper removeAccessTokenFromKeychainForIdentifier:AccessTokenKeychainIdentification]; + [self setAndStoreAccessToken:nil]; [self clearAuthorizationRequest]; diff --git a/SPiDSDK/SPiDSDK.h b/SPiDSDK/SPiDSDK.h index eb53b94..5d57060 100644 --- a/SPiDSDK/SPiDSDK.h +++ b/SPiDSDK/SPiDSDK.h @@ -20,7 +20,6 @@ FOUNDATION_EXPORT const unsigned char SPiDVersionString[]; #import "SPiDAccessToken.h" #import "SPiDClient.h" #import "SPiDJwt.h" -#import "SPiDKeychainWrapper.h" #import "SPiDRequest.h" #import "SPiDResponse.h" #import "SPiDStatus.h" diff --git a/SPiDSDK/SPiDTokenRequest.m b/SPiDSDK/SPiDTokenRequest.m index 443c082..672483f 100644 --- a/SPiDSDK/SPiDTokenRequest.m +++ b/SPiDSDK/SPiDTokenRequest.m @@ -7,7 +7,7 @@ #import "SPiDTokenRequest.h" #import "NSError+SPiD.h" -#import "SPiDKeychainWrapper.h" +#import "SPiDAccessToken.h" #import "SPiDJwt.h" @interface SPiDTokenRequest () @@ -69,6 +69,12 @@ - (instancetype)initPostTokenRequestWithPath:(NSString *)requestPath body:(NSDic @end + +@interface SPiDClient (SPiDClientPrivateAccessTokenSetter) +- (void)setAndStoreAccessToken:(SPiDAccessToken *)accessToken; +@end + + @implementation SPiDTokenRequest + (instancetype)clientTokenRequestWithCompletionHandler:(void (^)(NSError *error))completionHandler { @@ -215,8 +221,7 @@ - (void)startWithRequest:(NSURLRequest *)request { self.tokenCompletionHandler(error); } else /*if (_receivedData)*/ { SPiDAccessToken *accessToken = [[SPiDAccessToken alloc] initWithDictionary:jsonObject]; - [SPiDKeychainWrapper storeInKeychainAccessTokenWithValue:accessToken forIdentifier:AccessTokenKeychainIdentification]; - [[SPiDClient sharedInstance] setAccessToken:accessToken]; + [[SPiDClient sharedInstance] setAndStoreAccessToken:accessToken]; [[SPiDClient sharedInstance] authorizationComplete]; self.tokenCompletionHandler(nil); } diff --git a/SPiDSDK/SPiDTokenStorage.h b/SPiDSDK/SPiDTokenStorage.h new file mode 100644 index 0000000..73d5160 --- /dev/null +++ b/SPiDSDK/SPiDTokenStorage.h @@ -0,0 +1,39 @@ +// +// SPiDTokenStorage.h +// SPiDSDK +// +// Created by Daniel Lazarenko on 07/03/2017. +// + +#import + +@class SPiDAccessToken; + +@protocol SPiDTokenStorageBackend + +@required +- (SPiDAccessToken *)accessTokenForIdentifier:(NSString *)identifier; +- (BOOL)storeAccessTokenWithValue:(SPiDAccessToken *)accessToken forIdentifier:(NSString *)identifier; +- (BOOL)updateAccessTokenWithValue:(SPiDAccessToken *)accessToken forIdentifier:(NSString *)identifier; +- (void)removeAccessTokenForIdentifier:(NSString *)identifier; + +@end + + +typedef NS_ENUM(NSUInteger, SPiDTokenStorageBackendType) { + SPiDTokenStorageBackendTypeKeychain = 1, + SPiDTokenStorageBackendTypeUserDefaults, +}; + + +@interface SPiDTokenStorage : NSObject + +- (instancetype)initWithReadBackendTypes:(NSArray *)readBackendTypes + writeBackendTypes:(NSArray *)writeBackendTypes; + +- (SPiDAccessToken *)loadAccessTokenAndReplicate; +- (BOOL)storeAccessTokenWithValue:(SPiDAccessToken *)accessToken; +- (BOOL)updateAccessTokenWithValue:(SPiDAccessToken *)accessToken; +- (void)removeAccessToken; + +@end diff --git a/SPiDSDK/SPiDTokenStorage.m b/SPiDSDK/SPiDTokenStorage.m new file mode 100644 index 0000000..da5362d --- /dev/null +++ b/SPiDSDK/SPiDTokenStorage.m @@ -0,0 +1,154 @@ +// +// SPiDTokenStorage.m +// SPiDSDK +// +// Created by Daniel Lazarenko on 07/03/2017. +// + +#import "SPiDTokenStorage.h" +#import "SPiDTokenStorageUserDefaultsBackend.h" +#import "SPiDTokenStorageKeychainBackend.h" + +static NSString *const AccessTokenKeychainIdentification = @"AccessToken"; + +@interface SPiDTokenStorage () +{ + NSArray *_readBackendTypes; + NSArray *_writeBackendTypes; + NSDictionary> *_backends; +} + +@property (readonly) NSString *identifier; +@property (readonly) NSArray> *readBackends; +@property (readonly) NSArray> *writeBackends; + +@end + +@implementation SPiDTokenStorage + ++ (id)createBackendOfType:(SPiDTokenStorageBackendType)type +{ + switch (type) { + case SPiDTokenStorageBackendTypeKeychain: + return [SPiDTokenStorageKeychainBackend new]; + case SPiDTokenStorageBackendTypeUserDefaults: + return [SPiDTokenStorageUserDefaultsBackend new]; + default: + NSAssert(NO, @"Unknown SPiDTokenStorageBackendType %d", type); + } +} + +static BOOL haveCommonElementsInArrays(NSArray *array1, NSArray *array2) +{ + NSMutableSet *intersection = [NSMutableSet setWithArray:array1]; + [intersection intersectSet:[NSSet setWithArray:array2]]; + return (intersection.count > 0); +} + +- (instancetype)initWithReadBackendTypes:(NSArray *)readBackendTypes + writeBackendTypes:(NSArray *)writeBackendTypes +{ + __unused BOOL haveCommonReadAndWriteBackends = haveCommonElementsInArrays(readBackendTypes, writeBackendTypes); + NSAssert(haveCommonReadAndWriteBackends, @"At least one backend should be used for both reading and writing."); + + self = [super init]; + if (self == nil) return nil; + _readBackendTypes = readBackendTypes; + _writeBackendTypes = writeBackendTypes; + + NSSet *allBackendTypes = [[NSSet setWithArray:readBackendTypes] setByAddingObjectsFromArray:writeBackendTypes]; + NSMutableDictionary> *backends = [NSMutableDictionary new]; + for (NSNumber *type in allBackendTypes) { + backends[type] = [SPiDTokenStorage createBackendOfType:[type unsignedIntegerValue]]; + } + _backends = backends; + + return self; +} + +- (NSString *)identifier +{ + return AccessTokenKeychainIdentification; +} + +- (NSArray> *)readBackends +{ + NSMutableArray *backends = [NSMutableArray new]; + for (NSNumber *backendType in _readBackendTypes) { + [backends addObject:_backends[backendType]]; + } + return backends; +} + +- (NSArray> *)writeBackends +{ + NSMutableArray *backends = [NSMutableArray new]; + for (NSNumber *backendType in _writeBackendTypes) { + [backends addObject:_backends[backendType]]; + } + return backends; +} + +- (SPiDAccessToken *)loadAccessTokenAndReplicate +{ + SPiDAccessToken *token = nil; + NSNumber *tokenBackendType = nil; + for (NSNumber *backendType in _readBackendTypes) { + id backend = _backends[backendType]; + token = [backend accessTokenForIdentifier:self.identifier]; + if (token != nil) { + tokenBackendType = backendType; + break; + } + } + + if (token == nil) { + return nil; + } + + // replicate + NSMutableSet *replicateBackendTypes = [NSMutableSet setWithArray:_writeBackendTypes]; + [replicateBackendTypes removeObject:tokenBackendType]; + for (NSNumber *backendType in replicateBackendTypes) { + id backend = _backends[backendType]; + [backend storeAccessTokenWithValue:token forIdentifier:self.identifier]; + } + + // If we've loaded the token from a backend which is not set up for writing, + // it's safe to delete it there, because it's already replicated. + // Note: This would remove unsafe read backend data (NSUserDefaults) + // after an upgrade to a safe write backend (keychain). + if (![_writeBackendTypes containsObject:tokenBackendType]) { + id backend = _backends[tokenBackendType]; + [backend removeAccessTokenForIdentifier:self.identifier]; + } + + return token; +} + +- (BOOL)storeAccessTokenWithValue:(SPiDAccessToken *)accessToken +{ + BOOL result = YES; + for (id backend in self.writeBackends) { + result &= [backend storeAccessTokenWithValue:accessToken forIdentifier:self.identifier]; + } + return result; +} + +- (BOOL)updateAccessTokenWithValue:(SPiDAccessToken *)accessToken +{ + BOOL result = YES; + for (id backend in self.writeBackends) { + result &= [backend updateAccessTokenWithValue:accessToken forIdentifier:self.identifier]; + } + return result; +} + +- (void)removeAccessToken +{ + for (id backend in _backends.allValues) { + [backend removeAccessTokenForIdentifier:self.identifier]; + } +} + +@end diff --git a/SPiDSDK/SPiDTokenStorageKeychainBackend.h b/SPiDSDK/SPiDTokenStorageKeychainBackend.h new file mode 100644 index 0000000..fbcb90a --- /dev/null +++ b/SPiDSDK/SPiDTokenStorageKeychainBackend.h @@ -0,0 +1,13 @@ +// +// SPiDTokenStorageKeychainBackend.h +// SPiDSDK +// +// Created by Daniel Lazarenko on 07/03/2017. +// + +#import +#import "SPiDTokenStorage.h" + +@interface SPiDTokenStorageKeychainBackend : NSObject + +@end diff --git a/SPiDSDK/SPiDTokenStorageKeychainBackend.m b/SPiDSDK/SPiDTokenStorageKeychainBackend.m new file mode 100644 index 0000000..5555767 --- /dev/null +++ b/SPiDSDK/SPiDTokenStorageKeychainBackend.m @@ -0,0 +1,33 @@ +// +// SPiDTokenStorageKeychainBackend.m +// SPiDSDK +// +// Created by Daniel Lazarenko on 07/03/2017. +// + +#import "SPiDTokenStorageKeychainBackend.h" +#import "SPiDKeychainWrapper.h" + +@implementation SPiDTokenStorageKeychainBackend + +- (SPiDAccessToken *)accessTokenForIdentifier:(NSString *)identifier +{ + return [SPiDKeychainWrapper accessTokenFromKeychainForIdentifier:identifier]; +} + +- (BOOL)storeAccessTokenWithValue:(SPiDAccessToken *)accessToken forIdentifier:(NSString *)identifier +{ + return [SPiDKeychainWrapper storeInKeychainAccessTokenWithValue:accessToken forIdentifier:identifier]; +} + +- (BOOL)updateAccessTokenWithValue:(SPiDAccessToken *)accessToken forIdentifier:(NSString *)identifier +{ + return [SPiDKeychainWrapper updateAccessTokenInKeychainWithValue:accessToken forIdentifier:identifier]; +} + +- (void)removeAccessTokenForIdentifier:(NSString *)identifier +{ + [SPiDKeychainWrapper removeAccessTokenFromKeychainForIdentifier:identifier]; +} + +@end diff --git a/SPiDSDK/SPiDTokenStorageUserDefaultsBackend.h b/SPiDSDK/SPiDTokenStorageUserDefaultsBackend.h new file mode 100644 index 0000000..ea5d720 --- /dev/null +++ b/SPiDSDK/SPiDTokenStorageUserDefaultsBackend.h @@ -0,0 +1,13 @@ +// +// SPiDTokenStorageUserDefaultsBackend.h +// SPiDSDK +// +// Created by Daniel Lazarenko on 07/03/2017. +// + +#import +#import "SPiDTokenStorage.h" + +@interface SPiDTokenStorageUserDefaultsBackend : NSObject + +@end diff --git a/SPiDSDK/SPiDTokenStorageUserDefaultsBackend.m b/SPiDSDK/SPiDTokenStorageUserDefaultsBackend.m new file mode 100644 index 0000000..935ec79 --- /dev/null +++ b/SPiDSDK/SPiDTokenStorageUserDefaultsBackend.m @@ -0,0 +1,80 @@ +// +// SPiDTokenStorageUserDefaultsBackend.m +// SPiDSDK +// +// Created by Daniel Lazarenko on 07/03/2017. +// + +#import "SPiDTokenStorageUserDefaultsBackend.h" +#import "SPiDTokenStorage.h" +#import "SPiDAccessToken.h" + +static NSString *const SPiDAccessTokenUserIdKey = @"user_id"; +static NSString *const SPiDAccessTokenKey = @"access_token"; +static NSString *const SPiDAccessTokenExpiresAtKey = @"expires_at"; +static NSString *const SPiDAccessTokenRefreshTokenKey = @"refresh_token"; + +static NSString *const SPiDAccessTokenUserDefaultsPrefix = @"SPiD."; + +#define DICT_KEY [SPiDAccessTokenUserDefaultsPrefix stringByAppendingString:identifier] + + +@implementation SPiDTokenStorageUserDefaultsBackend +{ + NSUserDefaults *_defaults; +} + +- (instancetype)init +{ + self = [super init]; + if (self == nil) return nil; + _defaults = [NSUserDefaults standardUserDefaults]; + return self; +} + +- (SPiDAccessToken *)accessTokenForIdentifier:(NSString *)identifier +{ + NSDictionary *dict = [_defaults dictionaryForKey:DICT_KEY]; + NSString *userID = dict[SPiDAccessTokenUserIdKey]; + NSString *accessToken = dict[SPiDAccessTokenKey]; + NSNumber *expiresAtNum = dict[SPiDAccessTokenExpiresAtKey]; + NSDate *expiresAt = nil; + if (expiresAtNum != nil) { + expiresAt = [NSDate dateWithTimeIntervalSince1970:[expiresAtNum integerValue]]; + } + NSString *refreshToken = dict[SPiDAccessTokenRefreshTokenKey]; + return [[SPiDAccessToken alloc] initWithUserID:userID accessToken:accessToken + expiresAt:expiresAt refreshToken:refreshToken]; +} + +- (BOOL)storeAccessTokenWithValue:(SPiDAccessToken *)accessToken forIdentifier:(NSString *)identifier +{ + return [self updateAccessTokenWithValue:accessToken forIdentifier:identifier]; +} + +- (BOOL)updateAccessTokenWithValue:(SPiDAccessToken *)accessToken forIdentifier:(NSString *)identifier +{ + NSMutableDictionary *dict = [NSMutableDictionary new]; + if (accessToken.userID) { + dict[SPiDAccessTokenUserIdKey] = accessToken.userID; + } + if (accessToken.accessToken) { + dict[SPiDAccessTokenKey] = accessToken.accessToken; + } + if (accessToken.expiresAt) { + NSInteger expiresAtInt = (NSInteger)[accessToken.expiresAt timeIntervalSince1970]; + dict[SPiDAccessTokenExpiresAtKey] = @(expiresAtInt); + } + if (accessToken.refreshToken) { + dict[SPiDAccessTokenRefreshTokenKey] = accessToken.refreshToken; + } + [_defaults setObject:dict forKey:DICT_KEY]; + return YES; +} + +- (void)removeAccessTokenForIdentifier:(NSString *)identifier +{ + [_defaults removeObjectForKey:DICT_KEY]; +} + +@end