BTClientToken.m 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. #if __has_include(<Braintree/BraintreeCore.h>)
  2. #import <Braintree/BTClientToken.h>
  3. #import <Braintree/BTJSON.h>
  4. #else
  5. #import <BraintreeCore/BTClientToken.h>
  6. #import <BraintreeCore/BTJSON.h>
  7. #endif
  8. NSString *const BTClientTokenKeyVersion = @"version";
  9. NSString *const BTClientTokenKeyAuthorizationFingerprint = @"authorizationFingerprint";
  10. NSString *const BTClientTokenKeyConfigURL = @"configUrl";
  11. NSString * const BTClientTokenErrorDomain = @"com.braintreepayments.BTClientTokenErrorDomain";
  12. @interface BTClientToken ()
  13. @property (nonatomic, readwrite, copy) NSString *authorizationFingerprint;
  14. @property (nonatomic, readwrite, strong) NSURL *configURL;
  15. @property (nonatomic, copy) NSString *originalValue;
  16. @property (nonatomic, readwrite, strong) BTJSON *json;
  17. @end
  18. @implementation BTClientToken
  19. - (instancetype)init {
  20. return nil;
  21. }
  22. - (instancetype)initWithClientToken:(NSString *)clientToken error:(NSError * __autoreleasing *)error {
  23. if (self = [super init]) {
  24. // Client token must be decoded first because the other values are retrieved from it
  25. _json = [self decodeClientToken:clientToken error:error];
  26. _authorizationFingerprint = [_json[BTClientTokenKeyAuthorizationFingerprint] asString];
  27. _configURL = [_json[BTClientTokenKeyConfigURL] asURL];
  28. _originalValue = clientToken;
  29. if (![self validateClientToken:error]) {
  30. return nil;
  31. }
  32. }
  33. return self;
  34. }
  35. - (BOOL)validateClientToken:(NSError *__autoreleasing*)error {
  36. if (error != NULL && *error) {
  37. return NO;
  38. }
  39. if ([self.authorizationFingerprint length] == 0) {
  40. if (error != NULL) {
  41. *error = [NSError errorWithDomain:BTClientTokenErrorDomain
  42. code:BTClientTokenErrorInvalid
  43. userInfo:@{
  44. NSLocalizedDescriptionKey: @"Invalid client token. Please ensure your server is generating a valid Braintree ClientToken.",
  45. NSLocalizedFailureReasonErrorKey: @"Authorization fingerprint was not present or invalid." }];
  46. }
  47. return NO;
  48. }
  49. if (![self.configURL isKindOfClass:[NSURL class]] || self.configURL.absoluteString.length == 0) {
  50. if (error != NULL) {
  51. *error = [NSError errorWithDomain:BTClientTokenErrorDomain
  52. code:BTClientTokenErrorInvalid
  53. userInfo:@{
  54. NSLocalizedDescriptionKey: @"Invalid client token: config url was missing or invalid. Please ensure your server is generating a valid Braintree ClientToken."
  55. }];
  56. }
  57. return NO;
  58. }
  59. return YES;
  60. }
  61. - (instancetype)copyWithZone:(NSZone *)zone {
  62. BTClientToken *copiedClientToken = [[[self class] allocWithZone:zone] initWithClientToken:self.originalValue error:NULL];
  63. return copiedClientToken;
  64. }
  65. #pragma mark JSON Parsing
  66. - (NSDictionary *)parseJSONString:(NSString *)rawJSONString error:(NSError * __autoreleasing *)error {
  67. NSData *rawJSONData = [rawJSONString dataUsingEncoding:NSUTF8StringEncoding];
  68. return [NSJSONSerialization JSONObjectWithData:rawJSONData options:0 error:error];
  69. }
  70. #pragma mark NSCoding
  71. - (void)encodeWithCoder:(NSCoder *)coder {
  72. [coder encodeObject:self.originalValue forKey:@"originalValue"];
  73. }
  74. - (id)initWithCoder:(NSCoder *)decoder {
  75. return [self initWithClientToken:[decoder decodeObjectForKey:@"originalValue"] error:NULL];
  76. }
  77. #pragma mark Client Token Parsing
  78. - (BTJSON *)decodeClientToken:(NSString *)rawClientTokenString error:(NSError * __autoreleasing *)error {
  79. NSError *JSONError = nil;
  80. NSData *base64DecodedClientToken = [[NSData alloc] initWithBase64EncodedString:rawClientTokenString
  81. options:0];
  82. NSDictionary *rawClientToken;
  83. if (base64DecodedClientToken) {
  84. rawClientToken = [NSJSONSerialization JSONObjectWithData:base64DecodedClientToken options:0 error:&JSONError];
  85. } else {
  86. rawClientToken = [self parseJSONString:rawClientTokenString error:&JSONError];
  87. }
  88. if (!rawClientToken) {
  89. if (error) {
  90. NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] initWithDictionary:@{
  91. NSLocalizedDescriptionKey: @"Invalid client token. Please ensure your server is generating a valid Braintree ClientToken.",
  92. NSLocalizedFailureReasonErrorKey: @"Invalid JSON"
  93. }];
  94. if (JSONError) {
  95. userInfo[NSUnderlyingErrorKey] = JSONError;
  96. }
  97. *error = [NSError errorWithDomain:BTClientTokenErrorDomain
  98. code:BTClientTokenErrorInvalid
  99. userInfo:userInfo];
  100. }
  101. return nil;
  102. }
  103. if (![rawClientToken isKindOfClass:[NSDictionary class]]) {
  104. if (error) {
  105. *error = [NSError errorWithDomain:BTClientTokenErrorDomain
  106. code:BTClientTokenErrorInvalid
  107. userInfo:@{
  108. NSLocalizedDescriptionKey: @"Invalid client token. Please ensure your server is generating a valid Braintree ClientToken.",
  109. NSLocalizedFailureReasonErrorKey: @"Invalid JSON. Expected to find an object at JSON root."
  110. }];
  111. }
  112. return nil;
  113. }
  114. NSError *clientTokenFormatError = [NSError errorWithDomain:BTClientTokenErrorDomain
  115. code:BTClientTokenErrorInvalid
  116. userInfo:@{
  117. NSLocalizedDescriptionKey: @"Invalid client token format. Please pass the client token string directly as it is generated by the server-side SDK.",
  118. NSLocalizedFailureReasonErrorKey: @"Unsupported client token format."
  119. }];
  120. switch ([rawClientToken[BTClientTokenKeyVersion] integerValue]) {
  121. case 1:
  122. if (base64DecodedClientToken) {
  123. if (error) {
  124. *error = clientTokenFormatError;
  125. }
  126. return nil;
  127. }
  128. break;
  129. case 2:
  130. /* FALLTHROUGH */
  131. case 3:
  132. if (!base64DecodedClientToken) {
  133. if (error) {
  134. *error = clientTokenFormatError;
  135. }
  136. return nil;
  137. }
  138. break;
  139. default:
  140. if (error) {
  141. *error = [NSError errorWithDomain:BTClientTokenErrorDomain
  142. code:BTClientTokenErrorUnsupportedVersion
  143. userInfo:@{
  144. NSLocalizedDescriptionKey: @"Unsupported client token version. Please ensure your server is generating a valid Braintree ClientToken with a server-side SDK that is compatible with this version of Braintree iOS.",
  145. NSLocalizedFailureReasonErrorKey: @"Unsupported client token version."
  146. }];
  147. }
  148. return nil;
  149. }
  150. return [[BTJSON alloc] initWithValue:rawClientToken];
  151. }
  152. - (NSString *)description {
  153. return [NSString stringWithFormat:@"<BTClientToken: authorizationFingerprint:%@ configURL:%@>", self.authorizationFingerprint, self.configURL];
  154. }
  155. - (BOOL)isEqual:(id)object {
  156. if (self == object) {
  157. return YES;
  158. }
  159. if ([object isKindOfClass:[BTClientToken class]]) {
  160. BTClientToken *otherToken = object;
  161. return [self.json.asDictionary isEqualToDictionary:otherToken.json.asDictionary];
  162. }
  163. return NO;
  164. }
  165. @end