// // PPOTAppSwitchResponse.m // PayPalOneTouch // // Copyright © 2014 PayPal, Inc. All rights reserved. // #import "PPOTAppSwitchResponse.h" #if __has_include("PayPalUtils.h") #import "PPOTString.h" #import "PPOTTime.h" #import "PPOTEncryptionHelper.h" #import "PPOTJSONHelper.h" #else #import #import #import #import #endif @implementation PPOTAppSwitchResponse - (instancetype)initWithEncodedURL:(NSURL *)url encryptionKey:(NSString *)encryptionKey { if (!url) { return nil; } self = [self initWithURL:url]; if (self) { _encryptionKey = encryptionKey; // convert query string to dictionary, handles URLEncoding NSDictionary *query = [PPOTAppSwitchUtil parseQueryString:[url query]]; // base64 encoded payload NSString *encodedPayload = [PPOTJSONHelper stringFromDictionary:query withKey:kPPOTAppSwitchPayloadKey]; if (encodedPayload.length) { // parse payload _decodedPayload = [PPOTJSONHelper dictionaryWithBase64EncodedJSONString:encodedPayload]; } if (_encryptionKey.length) { // base64 encoded encrypted payload NSString *encodedEncryptedPayload = [PPOTJSONHelper stringFromDictionary:query withKey:kPPOTAppSwitchEncryptedPayloadKey]; if (encodedEncryptedPayload.length) { // parse encrypted payload NSData *encryptedData = [[NSData alloc] initWithBase64EncodedString:encodedEncryptedPayload options:NSDataBase64DecodingIgnoreUnknownCharacters]; [self parseEncryptedPayload:encryptedData]; } } [self parsePayload]; } return self; } - (instancetype)initWithHermesURL:(NSURL *)url environment:(NSString *)environment { if (!url) { return nil; } self = [self initWithURL:url]; if (self) { // TODO: add error parsing since hermes may pass a error or abroted flag. _responseType = PPAppSwitchResponseTypeWeb; _webURL = [url absoluteString]; _environment = environment; } return self; } - (instancetype)initWithURL:(NSURL *)url { self = [self init]; if (self) { // extract action from URL if ([[PPOTAppSwitchUtil actionFromURLAction:url] isEqualToString:kPPOTAppSwitchCancelAction]) { _action = PPAppSwitchResponseActionCancel; } else if ([[PPOTAppSwitchUtil actionFromURLAction:url] isEqualToString:kPPOTAppSwitchSuccessAction]) { _action = PPAppSwitchResponseActionSuccess; } else { _action = PPAppSwitchResponseActionUnknown; } } return self; } - (void)parsePayload { NSNumber *version = [PPOTJSONHelper numberFromDictionary:_decodedPayload withKey:kPPOTAppSwitchProtocolVersionKey]; // Wallet not always sends version, default to 1 _version = (version != nil) ? [version integerValue] : 1; // in version 3+ the response_type is no longer sent NSString *responseType = nil; if (_version >= 3) { responseType = [PPOTJSONHelper stringFromDictionary:_decodedPayload withKey:kPPOTAppSwitchPaymentCodeTypeKey]; if (responseType == nil) { responseType = [PPOTJSONHelper stringFromDictionary:_decodedPayload withKey:kPPOTAppSwitchResponseTypeKey]; } } else { responseType = [PPOTJSONHelper stringFromDictionary:_decodedPayload withKey:kPPOTAppSwitchResponseTypeKey]; } if ([responseType isEqualToString:kPPOTAppSwitchResponseTypeCode] || [responseType isEqualToString:kPPOTAppSwitchResposneAuthCodeKey]) { _responseType = PPAppSwitchResponseTypeAuthorizationCode; } else if ([responseType isEqualToString:kPPOTAppSwitchResponseTypeToken]) { _responseType = PPAppSwitchResponseTypeToken; } else if ([responseType isEqualToString:kPPOTAppSwitchResponseTypeWeb]) { _responseType = PPAppSwitchResponseTypeWeb; } else { _responseType = PPAppSwitchResponseTypeUnknown; } _displayName = [PPOTJSONHelper stringFromDictionary:_decodedPayload withKey:kPPOTAppSwitchDisplayNameKey]; _email = [PPOTJSONHelper stringFromDictionary:_decodedPayload withKey:kPPOTAppSwitchEmailKey]; // TODO: remove check until we fix with BT if (!_email.length) { _email = [_displayName copy]; } _accessToken = [PPOTJSONHelper stringFromDictionary:_decodedPayload withKey:kPPOTAppSwitchAccessTokenKey]; if (_version >= 3) { _authorizationCode = [PPOTJSONHelper stringFromDictionary:_decodedPayload withKey:kPPOTAppSwitchPaymentCodeKey];; if (![_authorizationCode length]) { _authorizationCode = [PPOTJSONHelper stringFromDictionary:_decodedPayload withKey:kPPOTAppSwitchAuthorizationCodeKey]; } } else { _authorizationCode = [PPOTJSONHelper stringFromDictionary:_decodedPayload withKey:kPPOTAppSwitchAuthorizationCodeKey]; } NSNumber *expiresIn = [PPOTJSONHelper numberFromDictionary:_decodedPayload withKey:kPPOTAppSwitchExpiresInKey]; if (expiresIn != nil) { _expiresIn = [expiresIn integerValue]; } NSString *scope = [PPOTJSONHelper stringFromDictionary:_decodedPayload withKey:kPPOTAppSwitchScopesKey]; if (scope.length) { _scope = [scope componentsSeparatedByString:@" "]; } _photoURL = [PPOTJSONHelper stringFromDictionary:_decodedPayload withKey:kPPOTAppSwitchPhotoURLKey]; _webURL = [PPOTJSONHelper stringFromDictionary:_decodedPayload withKey:kPPOTAppSwitchWebURLKey]; // The decoded payload may have been JSON decoded where the error value is already a dictionary NSDictionary *errorDict = [PPOTJSONHelper dictionaryFromDictionary:_decodedPayload withKey:kPPOTAppSwitchErrorKey]; if (errorDict) { _error = errorDict; } else { NSString *error = [PPOTJSONHelper stringFromDictionary:_decodedPayload withKey:kPPOTAppSwitchErrorKey]; if (error.length) { _error = [NSJSONSerialization JSONObjectWithData:[error dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil]; } } NSString *environment = [PPOTJSONHelper stringFromDictionary:_decodedPayload withKey:kPPOTAppSwitchEnvironmentKey]; if (environment.length) { _environment = [environment lowercaseString]; } // TODO: BT not sending at a string and in weird format NSString *strTimetamp = [PPOTJSONHelper stringFromDictionary:_decodedPayload withKey:kPPOTAppSwitchTimestampKey]; if (strTimetamp) { _timeStamp = [PPOTTime dateFromRFC3339LikeString:strTimetamp]; } else { NSNumber *timestamp = [PPOTJSONHelper numberFromDictionary:_decodedPayload withKey:kPPOTAppSwitchTimestampKey]; if (timestamp != nil) { _timeStamp = [NSDate dateWithTimeIntervalSince1970:[timestamp doubleValue]]; } } _msgID = [PPOTJSONHelper stringFromDictionary:_decodedPayload withKey:kPPOTAppSwitchMsgGUIDKey]; } - (void)parseEncryptedPayload:(NSData *)encryptedPayload { NSData *encryptionKey = [PPOTString dataWithHexString:_encryptionKey]; NSData *decryptedPayload = [PPOTEncryptionHelper decryptAESCTRData:encryptedPayload encryptionKey:encryptionKey]; if (decryptedPayload.length && encryptionKey.length) { NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:decryptedPayload options:0 error:nil]; if (jsonDictionary) { // merge with existing payload NSMutableDictionary *mergedDictionary = [NSMutableDictionary dictionaryWithCapacity:_decodedPayload.count + jsonDictionary.count]; [mergedDictionary addEntriesFromDictionary:_decodedPayload]; [mergedDictionary addEntriesFromDictionary:jsonDictionary]; _decodedPayload = mergedDictionary; } } } - (BOOL)validResponse { if (self.action == PPAppSwitchResponseActionCancel) { return YES; } if (self.responseType == PPAppSwitchResponseTypeUnknown || self.action == PPAppSwitchResponseActionUnknown || (self.version < 0 || self.version > kPPOTAppSwitchCurrentVersionNumber) ) { return NO; } // TOKEN not supported if (self.responseType == PPAppSwitchResponseTypeAuthorizationCode) { if (!self.authorizationCode.length || !self.email.length || !self.displayName.length) { return NO; } } if (self.responseType == PPAppSwitchResponseTypeWeb) { if (!self.webURL.length) { return NO; } } return YES; } - (NSString *)description { return [_decodedPayload description]; } @end