PPOTConfiguration.m 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. //
  2. // PPOTConfiguration.m
  3. // PayPalOneTouch
  4. //
  5. // Copyright © 2015 PayPal, Inc. All rights reserved.
  6. //
  7. #import "PPOTConfiguration.h"
  8. #if __has_include("PayPalUtils.h")
  9. #import "PPOTJSONHelper.h"
  10. #import "PPOTMacros.h"
  11. #import "PPOTSimpleKeychain.h"
  12. #import "PPOTURLSession.h"
  13. #else
  14. #import <PayPalUtils/PPOTJSONHelper.h>
  15. #import <PayPalUtils/PPOTMacros.h>
  16. #import <PayPalUtils/PPOTSimpleKeychain.h>
  17. #import <PayPalUtils/PPOTURLSession.h>
  18. #endif
  19. #if __has_include("BraintreeCore.h")
  20. #import "BTLogger_Internal.h"
  21. #else
  22. #import <BraintreeCore/BTLogger_Internal.h>
  23. #endif
  24. #import <libkern/OSAtomic.h>
  25. #include "PPDefaultConfigurationJSON.h"
  26. // `PPDefaultConfigurationJSON.h` is generated by a build script from `otc-config.ios.json`;
  27. // it defines these two variables:
  28. // unsigned char configuration_otc_config_ios_json[];
  29. // unsigned int configuration_otc_config_ios_json_len;
  30. // `configuration_otc_config_ios_json` holds the contents of the `otc-config.ios.json` file.
  31. #define PPEnvironmentProduction @"live"
  32. #define kConfigurationFileDownloadURL CARDIO_STR(@"https://www.paypalobjects.com/webstatic/otc/otc-config.ios.json")
  33. #define kConfigurationFileDownloadTimeout 60
  34. #define kConfigurationFileDownloadRetryInterval (5 * 60) // 5 minutes
  35. #define kPPOTConfigurationFileMaximumAcceptableObsolescence (4 * 60 * 60) // 4 hours
  36. #define kPPOTConfigurationSupportedProtocolVersionsForWallet @[@1, @2, @3]
  37. #define kPPOTConfigurationSupportedProtocolVersionsForBrowser @[@0, @3]
  38. #define kPPOTConfigurationKeyOs CARDIO_STR(@"os")
  39. #define kPPOTConfigurationKeyFileTimestamp CARDIO_STR(@"file_timestamp")
  40. #define kPPOTConfigurationKeyTarget CARDIO_STR(@"target")
  41. #define kPPOTConfigurationKeyProtocolVersion CARDIO_STR(@"protocol")
  42. #define kPPOTConfigurationKeySupportedLocales CARDIO_STR(@"supported_locales")
  43. #define kPPOTConfigurationKeyScope CARDIO_STR(@"scope")
  44. #define kPPOTConfigurationKeyURLScheme CARDIO_STR(@"scheme")
  45. #define kPPOTConfigurationKeyApplications CARDIO_STR(@"applications")
  46. #define kPPOTConfigurationKeyEndpoints CARDIO_STR(@"endpoints")
  47. #define kPPOTConfigurationKeyURL CARDIO_STR(@"url")
  48. #define kPPOTConfigurationKeyCertificateSerialNumber CARDIO_STR(@"certificate_serial_number")
  49. #define kPPOTConfigurationKeyCertificate CARDIO_STR(@"certificate")
  50. #define kPPOTConfigurationKeyOAuthRecipes CARDIO_STR(@"oauth2_recipes_in_decreasing_priority_order")
  51. #define kPPOTConfigurationKeyCheckoutRecipes CARDIO_STR(@"checkout_recipes_in_decreasing_priority_order")
  52. #define kPPOTConfigurationKeyBillingAgreementRecipes CARDIO_STR(@"billing_agreement_recipes_in_decreasing_priority_order")
  53. #define kPPOTConfigurationValueWallet CARDIO_STR(@"wallet")
  54. #define kPPOTConfigurationValueBrowser CARDIO_STR(@"browser")
  55. #define kPPOTCoderKeyConfigurationRecipeTarget CARDIO_STR(@"target")
  56. #define kPPOTCoderKeyConfigurationRecipeProtocolVersion CARDIO_STR(@"protocol")
  57. #define kPPOTCoderKeyConfigurationRecipeSupportedLocales CARDIO_STR(@"supportedLocales")
  58. #define kPPOTCoderKeyConfigurationRecipeTargetAppURLScheme CARDIO_STR(@"targetAppURLScheme")
  59. #define kPPOTCoderKeyConfigurationRecipeTargetAppBundleIDs CARDIO_STR(@"targetAppBundleIDs")
  60. #define kPPOTCoderKeyConfigurationRecipeEndpoints CARDIO_STR(@"endpoints")
  61. #define kPPOTCoderKeyConfigurationRecipeScope CARDIO_STR(@"scope")
  62. #define kPPOTCoderKeyConfigurationRecipeURL CARDIO_STR(@"url")
  63. #define kPPOTCoderKeyConfigurationRecipeCertificateSerialNumber CARDIO_STR(@"certificate_serial_number")
  64. #define kPPOTCoderKeyConfigurationRecipeCertificate CARDIO_STR(@"certificate")
  65. #define kPPOTCoderKeyConfigurationDownloadTime CARDIO_STR(@"downloadTime")
  66. #define kPPOTCoderKeyConfigurationTimestamp CARDIO_STR(@"timestamp")
  67. #define kPPOTCoderKeyConfigurationOAuthRecipes CARDIO_STR(@"oAuthRecipes")
  68. #define kPPOTCoderKeyConfigurationCheckoutRecipes CARDIO_STR(@"checkoutRecipes")
  69. #define kPPOTCoderKeyConfigurationBillingAgreementRecipes CARDIO_STR(@"billingAgreementRecipes")
  70. #define kPPOTKeychainConfiguration CARDIO_STR(@"PayPal_OTC_Configuration")
  71. #define LOG_ERROR_AND_RETURN_NIL { PPSDKLog(@"Bad configuration: error %d", __LINE__); return nil; }
  72. #define STRING_FROM_DICTIONARY(STRING, DICTIONARY, KEY) \
  73. NSString *STRING = [PPOTJSONHelper stringFromDictionary:DICTIONARY withKey:KEY]; \
  74. if (!STRING) LOG_ERROR_AND_RETURN_NIL
  75. #define DICTIONARY_FROM_DICTIONARY(DICTIONARY1, DICTIONARY2, KEY, REQUIRED) \
  76. NSDictionary *DICTIONARY1 = [PPOTJSONHelper dictionaryFromDictionary:DICTIONARY2 withKey:KEY]; \
  77. if (REQUIRED && !DICTIONARY1) LOG_ERROR_AND_RETURN_NIL
  78. #define STRING_ARRAY_FROM_DICTIONARY(ARRAY, DICTIONARY, KEY, REQUIRED) \
  79. NSArray *ARRAY = [PPOTJSONHelper stringArrayFromDictionary:DICTIONARY withKey:KEY]; \
  80. if (REQUIRED && !ARRAY) LOG_ERROR_AND_RETURN_NIL
  81. #define DICTIONARY_ARRAY_FROM_DICTIONARY(ARRAY, DICTIONARY, KEY, REQUIRED) \
  82. NSArray *ARRAY = [PPOTJSONHelper dictionaryArrayFromDictionary:DICTIONARY withKey:KEY]; \
  83. if (REQUIRED && !ARRAY) LOG_ERROR_AND_RETURN_NIL
  84. #pragma mark - PPOTConfigurationRecipe
  85. @implementation PPOTConfigurationRecipe
  86. - (instancetype)initWithDictionary:(NSDictionary *)dictionary {
  87. STRING_FROM_DICTIONARY(targetString, dictionary, kPPOTConfigurationKeyTarget)
  88. STRING_FROM_DICTIONARY(protocolVersionString, dictionary, kPPOTConfigurationKeyProtocolVersion)
  89. NSNumber *protocolVersionNumber = [NSNumber numberWithInteger:[protocolVersionString integerValue]];
  90. if ((self = [super init])) {
  91. if ([targetString isEqualToString:kPPOTConfigurationValueWallet]) {
  92. _target = PPOTRequestTargetOnDeviceApplication;
  93. if (![kPPOTConfigurationSupportedProtocolVersionsForWallet containsObject:protocolVersionNumber]) {
  94. LOG_ERROR_AND_RETURN_NIL
  95. }
  96. _protocolVersion = protocolVersionNumber;
  97. STRING_ARRAY_FROM_DICTIONARY(supportedLocalesArray, dictionary, kPPOTConfigurationKeySupportedLocales, NO)
  98. // protect against capitalization mistakes:
  99. NSMutableArray *uppercasedSupportedLocalesArray = [NSMutableArray arrayWithCapacity:[supportedLocalesArray count]];
  100. for (NSString *locale in supportedLocalesArray) {
  101. [uppercasedSupportedLocalesArray addObject:[locale uppercaseString]];
  102. }
  103. _supportedLocales = uppercasedSupportedLocalesArray;
  104. STRING_FROM_DICTIONARY(targetAppURLScheme, dictionary, kPPOTConfigurationKeyURLScheme)
  105. if ([targetAppURLScheme rangeOfString:@":"].location != NSNotFound ||
  106. [targetAppURLScheme rangeOfString:@"/"].location != NSNotFound) {
  107. LOG_ERROR_AND_RETURN_NIL
  108. }
  109. _targetAppURLScheme = targetAppURLScheme;
  110. STRING_ARRAY_FROM_DICTIONARY(targetsArray, dictionary, kPPOTConfigurationKeyApplications, YES)
  111. _targetAppBundleIDs = targetsArray;
  112. }
  113. else if ([targetString isEqualToString:kPPOTConfigurationValueBrowser]) {
  114. _target = PPOTRequestTargetBrowser;
  115. if (![kPPOTConfigurationSupportedProtocolVersionsForBrowser containsObject:protocolVersionNumber]) {
  116. LOG_ERROR_AND_RETURN_NIL
  117. }
  118. _protocolVersion = protocolVersionNumber;
  119. }
  120. else {
  121. LOG_ERROR_AND_RETURN_NIL
  122. }
  123. }
  124. return self;
  125. }
  126. #pragma mark - NSCoding
  127. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  128. if ((self = [self init])) {
  129. _target = ((NSNumber *)[aDecoder decodeObjectForKey:kPPOTCoderKeyConfigurationRecipeTarget]).unsignedIntegerValue;
  130. _protocolVersion = [aDecoder decodeObjectForKey:kPPOTCoderKeyConfigurationRecipeProtocolVersion];
  131. if (_target == PPOTRequestTargetOnDeviceApplication) {
  132. _targetAppURLScheme = [aDecoder decodeObjectForKey:kPPOTCoderKeyConfigurationRecipeTargetAppURLScheme];
  133. _targetAppBundleIDs = [aDecoder decodeObjectForKey:kPPOTCoderKeyConfigurationRecipeTargetAppBundleIDs];
  134. _supportedLocales = [aDecoder decodeObjectForKey:kPPOTCoderKeyConfigurationRecipeSupportedLocales];
  135. }
  136. }
  137. return self;
  138. }
  139. - (void)encodeWithCoder:(NSCoder *)aCoder {
  140. [aCoder encodeObject:@(self.target) forKey:kPPOTCoderKeyConfigurationRecipeTarget];
  141. [aCoder encodeObject:self.protocolVersion forKey:kPPOTCoderKeyConfigurationRecipeProtocolVersion];
  142. if (self.target == PPOTRequestTargetOnDeviceApplication) {
  143. [aCoder encodeObject:self.targetAppURLScheme forKey:kPPOTCoderKeyConfigurationRecipeTargetAppURLScheme];
  144. [aCoder encodeObject:self.targetAppBundleIDs forKey:kPPOTCoderKeyConfigurationRecipeTargetAppBundleIDs];
  145. [aCoder encodeObject:self.supportedLocales forKey:kPPOTCoderKeyConfigurationRecipeSupportedLocales];
  146. }
  147. }
  148. @end
  149. #pragma mark - PPOTConfigurationRecipeEndpoint
  150. @implementation PPOTConfigurationRecipeEndpoint
  151. - (instancetype)initWithURL:(NSString *)url withCertificateSerialNumber:(NSString *)certificateSerialNumber withBase64EncodedCertificate:(NSString *)base64EncodedCertificate {
  152. if ((self = [super init])) {
  153. if (![url length] || ![certificateSerialNumber length] || ![base64EncodedCertificate length]) {
  154. LOG_ERROR_AND_RETURN_NIL
  155. }
  156. if (![url hasPrefix:@"https://"] && ![url hasPrefix:@"http://"]) {
  157. LOG_ERROR_AND_RETURN_NIL
  158. }
  159. _url = url;
  160. _certificateSerialNumber = certificateSerialNumber;
  161. _base64EncodedCertificate = base64EncodedCertificate;
  162. }
  163. return self;
  164. }
  165. #pragma mark - NSCoding
  166. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  167. if ((self = [super init])) {
  168. _url = [aDecoder decodeObjectForKey:kPPOTCoderKeyConfigurationRecipeURL];
  169. _certificateSerialNumber = [aDecoder decodeObjectForKey:kPPOTCoderKeyConfigurationRecipeCertificateSerialNumber];
  170. _base64EncodedCertificate = [aDecoder decodeObjectForKey:kPPOTCoderKeyConfigurationRecipeCertificate];
  171. }
  172. return self;
  173. }
  174. - (void)encodeWithCoder:(NSCoder *)aCoder {
  175. [aCoder encodeObject:self.url forKey:kPPOTCoderKeyConfigurationRecipeURL];
  176. [aCoder encodeObject:self.certificateSerialNumber forKey:kPPOTCoderKeyConfigurationRecipeCertificateSerialNumber];
  177. [aCoder encodeObject:self.base64EncodedCertificate forKey:kPPOTCoderKeyConfigurationRecipeCertificate];
  178. }
  179. @end
  180. #pragma mark - PPOTConfigurationOAuthRecipe
  181. @implementation PPOTConfigurationOAuthRecipe
  182. - (instancetype)initWithDictionary:(NSDictionary *)dictionary {
  183. if ((self = [super initWithDictionary:dictionary])) {
  184. STRING_ARRAY_FROM_DICTIONARY(scopeStrings, dictionary, kPPOTConfigurationKeyScope, YES)
  185. _scope = [NSSet setWithArray:scopeStrings];
  186. DICTIONARY_FROM_DICTIONARY(jsonEndpoints, dictionary, kPPOTConfigurationKeyEndpoints,
  187. self.target == PPOTRequestTargetBrowser && [self.protocolVersion isEqual:@(3)])
  188. if (![jsonEndpoints count]) {
  189. _endpoints = nil;
  190. }
  191. else {
  192. NSMutableDictionary *endpoints = [NSMutableDictionary dictionaryWithCapacity:[jsonEndpoints count]];
  193. for (NSString *environment in jsonEndpoints) {
  194. NSString *url = jsonEndpoints[environment][kPPOTConfigurationKeyURL];
  195. NSString *certificateSerialNumber = jsonEndpoints[environment][kPPOTConfigurationKeyCertificateSerialNumber];
  196. NSString *base64EncodedCertificate = jsonEndpoints[environment][kPPOTConfigurationKeyCertificate];
  197. PPOTConfigurationRecipeEndpoint *endpoint = [[PPOTConfigurationRecipeEndpoint alloc] initWithURL:url
  198. withCertificateSerialNumber:certificateSerialNumber
  199. withBase64EncodedCertificate:base64EncodedCertificate];
  200. if (!endpoint) {
  201. LOG_ERROR_AND_RETURN_NIL
  202. }
  203. endpoints[environment] = endpoint;
  204. }
  205. if (!endpoints[PPEnvironmentProduction]) {
  206. LOG_ERROR_AND_RETURN_NIL
  207. }
  208. _endpoints = endpoints;
  209. }
  210. }
  211. return self;
  212. }
  213. #pragma mark - NSCoding
  214. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  215. if ((self = [super initWithCoder:aDecoder])) {
  216. _scope = [aDecoder decodeObjectForKey:kPPOTCoderKeyConfigurationRecipeScope];
  217. if (self.target == PPOTRequestTargetBrowser) {
  218. _endpoints = [aDecoder decodeObjectForKey:kPPOTCoderKeyConfigurationRecipeEndpoints];
  219. }
  220. }
  221. return self;
  222. }
  223. - (void)encodeWithCoder:(NSCoder *)aCoder {
  224. [super encodeWithCoder:aCoder];
  225. [aCoder encodeObject:self.scope forKey:kPPOTCoderKeyConfigurationRecipeScope];
  226. if (self.target == PPOTRequestTargetBrowser) {
  227. [aCoder encodeObject:self.endpoints forKey:kPPOTCoderKeyConfigurationRecipeEndpoints];
  228. }
  229. }
  230. @end
  231. #pragma mark - PPOTConfigurationCheckoutRecipe
  232. @implementation PPOTConfigurationCheckoutRecipe
  233. - (instancetype)initWithDictionary:(NSDictionary *)dict {
  234. if ((self = [super initWithDictionary:dict])) {
  235. // no subclass-specific properties, so far
  236. }
  237. return self;
  238. }
  239. #pragma mark - NSCoding
  240. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  241. if ((self = [super initWithCoder:aDecoder])) {
  242. // no subclass-specific properties, so far
  243. }
  244. return self;
  245. }
  246. - (void)encodeWithCoder:(NSCoder *)aCoder {
  247. [super encodeWithCoder:aCoder];
  248. // no subclass-specific properties, so far
  249. }
  250. @end
  251. #pragma mark - PPOTConfigurationBillingAgreementRecipe
  252. @implementation PPOTConfigurationBillingAgreementRecipe
  253. - (instancetype)initWithDictionary:(NSDictionary *)dict {
  254. if ((self = [super initWithDictionary:dict])) {
  255. // no subclass-specific properties, so far
  256. }
  257. return self;
  258. }
  259. #pragma mark - NSCoding
  260. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  261. if ((self = [super initWithCoder:aDecoder])) {
  262. // no subclass-specific properties, so far
  263. }
  264. return self;
  265. }
  266. - (void)encodeWithCoder:(NSCoder *)aCoder {
  267. [super encodeWithCoder:aCoder];
  268. // no subclass-specific properties, so far
  269. }
  270. @end
  271. #pragma mark - PPOTConfiguration
  272. typedef void (^PPOTConfigurationFileDownloadCompletionBlock)(NSData *fileData);
  273. @interface PPOTConfiguration ()
  274. @property (nonatomic, strong, readwrite) NSDate *downloadTime;
  275. @end
  276. @implementation PPOTConfiguration
  277. #pragma mark - debug-only stuff
  278. #ifdef DEBUG
  279. static BOOL alwaysUseHardcodedConfiguration = NO;
  280. + (void)useHardcodedConfiguration:(BOOL)useHardcodedConfiguration {
  281. alwaysUseHardcodedConfiguration = useHardcodedConfiguration;
  282. }
  283. #endif
  284. #pragma mark - public methods
  285. + (void)updateCacheAsNecessary {
  286. // If there is no persisted configuration, or if it's stale,
  287. // then download a fresh configuration file and persist it.
  288. static int nobodyIsWorkingOnThisAtTheMoment = 1;
  289. #pragma clang diagnostic push
  290. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  291. if (OSAtomicCompareAndSwapInt(1, 0, &nobodyIsWorkingOnThisAtTheMoment)) {
  292. PPOTConfiguration *currentConfiguration = [PPOTConfiguration fetchPersistentConfiguration];
  293. if (!currentConfiguration || fabs([currentConfiguration.downloadTime timeIntervalSinceNow]) > kPPOTConfigurationFileMaximumAcceptableObsolescence) {
  294. static NSDate *lastConfigurationFileDownloadAttemptTime = nil;
  295. if (!lastConfigurationFileDownloadAttemptTime ||
  296. fabs([lastConfigurationFileDownloadAttemptTime timeIntervalSinceNow]) > kConfigurationFileDownloadRetryInterval) {
  297. lastConfigurationFileDownloadAttemptTime = [NSDate date];
  298. NSURL *url = [NSURL URLWithString:kConfigurationFileDownloadURL];
  299. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
  300. [request setHTTPMethod:@"GET"];
  301. // TODO: Can simplify by not specifying timeout interval. This might be better anyway to not specify because of slow networks.
  302. PPOTURLSession *session = [PPOTURLSession sessionWithTimeoutIntervalForRequest:kConfigurationFileDownloadTimeout];
  303. [session sendRequest:request
  304. completionBlock:^(NSData *data, __attribute__((unused)) NSHTTPURLResponse *response, __attribute__((unused)) NSError *error) {
  305. #ifdef DEBUG
  306. NSString *dataString = nil;
  307. if (data) {
  308. dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  309. }
  310. else {
  311. dataString = @"<no data received>";
  312. }
  313. [[BTLogger sharedLogger] debug:@"Downloaded JSON config\n-> HTTP status: %ld\n-> file contents:\n%@\n", (long)response.statusCode, dataString];
  314. #endif
  315. PPOTConfiguration *configuration = data ? [[PPOTConfiguration alloc] initWithJSON:data] : nil;
  316. if (configuration) {
  317. configuration.downloadTime = [NSDate date];
  318. [PPOTConfiguration storePersistentConfiguration:configuration];
  319. }
  320. [session finishTasksAndInvalidate];
  321. }];
  322. }
  323. }
  324. nobodyIsWorkingOnThisAtTheMoment = 1;
  325. }
  326. #pragma clang diagnostic pop
  327. }
  328. + (PPOTConfiguration *)getCurrentConfiguration {
  329. #ifdef DEBUG
  330. if (alwaysUseHardcodedConfiguration) {
  331. return [self defaultConfiguration];
  332. }
  333. #endif
  334. PPOTConfiguration *currentConfiguration = [PPOTConfiguration fetchPersistentConfiguration];
  335. if (!currentConfiguration) {
  336. currentConfiguration = [self defaultConfiguration];
  337. }
  338. return currentConfiguration;
  339. }
  340. + (PPOTConfiguration *)configurationWithDictionary:(NSDictionary *)dictionary {
  341. return [[PPOTConfiguration alloc] initWithDictionary:dictionary];
  342. }
  343. #pragma mark - private methods
  344. + (void)initialize {
  345. #ifdef DEBUG
  346. NSAssert([PPOTConfiguration defaultConfiguration] != nil, @"otc-config.ios.json is invalid");
  347. #endif
  348. if (self == [PPOTConfiguration class]) {
  349. [self updateCacheAsNecessary];
  350. }
  351. }
  352. + (PPOTConfiguration *)defaultConfiguration {
  353. NSData *defaultConfigurationJSON = [NSData dataWithBytes:configuration_otc_config_ios_json
  354. length:configuration_otc_config_ios_json_len];
  355. #ifdef DEBUG
  356. NSString *str = [[NSString alloc] initWithData:defaultConfigurationJSON encoding:NSUTF8StringEncoding];
  357. [[BTLogger sharedLogger] debug:@"Using default JSON config %@\n", str];
  358. #endif
  359. PPOTConfiguration *defaultConfiguration = [[PPOTConfiguration alloc] initWithJSON:defaultConfigurationJSON];
  360. return defaultConfiguration;
  361. }
  362. - (instancetype)initWithDictionary:(NSDictionary *)dictionary {
  363. STRING_FROM_DICTIONARY(os, dictionary, kPPOTConfigurationKeyOs)
  364. if (![os isEqualToString:@"iOS"]) {
  365. LOG_ERROR_AND_RETURN_NIL
  366. }
  367. STRING_FROM_DICTIONARY(fileTimestamp, dictionary, kPPOTConfigurationKeyFileTimestamp)
  368. // Currently we only support config file format 1.0.
  369. // If we ever need to update the file format, then the code here would presumably
  370. // first look for sub-dictionary "2.0" (or whatever) and then fallback to "1.0" as needed.
  371. DICTIONARY_FROM_DICTIONARY(subDictionary, dictionary, @"1.0", YES)
  372. DICTIONARY_ARRAY_FROM_DICTIONARY(prioritizedOAuthRecipesDictionaries, subDictionary, kPPOTConfigurationKeyOAuthRecipes, NO)
  373. DICTIONARY_ARRAY_FROM_DICTIONARY(prioritizedCheckoutRecipesDictionaries, subDictionary, kPPOTConfigurationKeyCheckoutRecipes, NO)
  374. DICTIONARY_ARRAY_FROM_DICTIONARY(prioritizedBillingAgreementRecipesDictionaries, subDictionary, kPPOTConfigurationKeyBillingAgreementRecipes, NO)
  375. if ((self = [super init])) {
  376. _downloadTime = [NSDate dateWithTimeIntervalSince1970:0]; // by default, mark file as obsolete
  377. _fileTimestamp = fileTimestamp;
  378. _prioritizedOAuthRecipes = [self prioritizedRecipesFromArray:prioritizedOAuthRecipesDictionaries withRecipeAdapter:^PPOTConfigurationRecipe *(NSDictionary *recipeDictionary) {
  379. return [[PPOTConfigurationOAuthRecipe alloc] initWithDictionary:recipeDictionary];
  380. }];
  381. _prioritizedCheckoutRecipes = [self prioritizedRecipesFromArray:prioritizedCheckoutRecipesDictionaries withRecipeAdapter:^PPOTConfigurationRecipe* (NSDictionary* recipeDictionary) {
  382. return [[PPOTConfigurationCheckoutRecipe alloc] initWithDictionary:recipeDictionary];
  383. }];
  384. _prioritizedBillingAgreementRecipes = [self prioritizedRecipesFromArray:prioritizedBillingAgreementRecipesDictionaries withRecipeAdapter:^PPOTConfigurationRecipe* (NSDictionary* recipeDictionary) {
  385. return [[PPOTConfigurationBillingAgreementRecipe alloc] initWithDictionary:recipeDictionary];
  386. }];
  387. if (!_prioritizedOAuthRecipes || !_prioritizedCheckoutRecipes || !_prioritizedBillingAgreementRecipes) {
  388. return nil;
  389. }
  390. }
  391. return self;
  392. }
  393. - (NSArray*)prioritizedRecipesFromArray:(NSArray*)recipes withRecipeAdapter:(PPOTConfigurationRecipe* (^)(NSDictionary*))recipeAdapter {
  394. NSMutableArray *prioritizedRecipes = [NSMutableArray arrayWithCapacity:[recipes count]];
  395. for (NSDictionary *recipeDictionary in recipes) {
  396. PPOTConfigurationRecipe *recipe = recipeAdapter(recipeDictionary);
  397. if (recipe) {
  398. [prioritizedRecipes addObject:recipe];
  399. }
  400. }
  401. return prioritizedRecipes;
  402. }
  403. - (instancetype)initWithJSON:(NSData *)jsonData {
  404. NSError *error = nil;
  405. id jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
  406. if (error || ![jsonObject isKindOfClass:[NSDictionary class]]) {
  407. LOG_ERROR_AND_RETURN_NIL
  408. }
  409. self = [self initWithDictionary:((NSDictionary *)jsonObject)];
  410. return self;
  411. }
  412. #pragma mark - description
  413. - (NSString *)description {
  414. return [NSString stringWithFormat:@"PPOTConfiguration: %ld Authorization recipes, %ld Checkout recipes, %ld Billing Agreement recipes",
  415. (unsigned long)[self.prioritizedOAuthRecipes count],
  416. (unsigned long)[self.prioritizedCheckoutRecipes count],
  417. (unsigned long)[self.prioritizedBillingAgreementRecipes count]];
  418. }
  419. #pragma mark - NSCoding
  420. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  421. if ((self = [self init])) {
  422. _downloadTime = [aDecoder decodeObjectForKey:kPPOTCoderKeyConfigurationDownloadTime];
  423. _fileTimestamp = [aDecoder decodeObjectForKey:kPPOTCoderKeyConfigurationTimestamp];
  424. _prioritizedOAuthRecipes = [aDecoder decodeObjectForKey:kPPOTCoderKeyConfigurationOAuthRecipes];
  425. _prioritizedCheckoutRecipes = [aDecoder decodeObjectForKey:kPPOTCoderKeyConfigurationCheckoutRecipes];
  426. _prioritizedBillingAgreementRecipes = [aDecoder decodeObjectForKey:kPPOTCoderKeyConfigurationBillingAgreementRecipes];
  427. }
  428. return self;
  429. }
  430. - (void)encodeWithCoder:(NSCoder *)aCoder {
  431. [aCoder encodeObject:self.downloadTime forKey:kPPOTCoderKeyConfigurationDownloadTime];
  432. [aCoder encodeObject:self.fileTimestamp forKey:kPPOTCoderKeyConfigurationTimestamp];
  433. [aCoder encodeObject:self.prioritizedOAuthRecipes forKey:kPPOTCoderKeyConfigurationOAuthRecipes];
  434. [aCoder encodeObject:self.prioritizedCheckoutRecipes forKey:kPPOTCoderKeyConfigurationCheckoutRecipes];
  435. [aCoder encodeObject:self.prioritizedBillingAgreementRecipes forKey:kPPOTCoderKeyConfigurationBillingAgreementRecipes];
  436. }
  437. #pragma mark - keychain persistence
  438. + (PPOTConfiguration *)fetchPersistentConfiguration {
  439. return (PPOTConfiguration *) [PPOTSimpleKeychain unarchiveObjectWithDataForKey:kPPOTKeychainConfiguration];
  440. }
  441. + (void)storePersistentConfiguration:(PPOTConfiguration *)configuration {
  442. NSData *data = [NSKeyedArchiver archivedDataWithRootObject:configuration];
  443. [PPOTSimpleKeychain setData:data forKey:kPPOTKeychainConfiguration];
  444. }
  445. @end
  446. @implementation PPConfiguration
  447. @end
  448. @implementation PPConfigurationCheckoutRecipe
  449. @end
  450. @implementation PPConfigurationBillingAgreementRecipe
  451. @end
  452. @implementation PPConfigurationOAuthRecipe
  453. @end
  454. @implementation PPConfigurationRecipeEndpoint
  455. @end