BTClientToken.m 8.0 KB

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