password_spec_fetcher.mm 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #import "ios/components/credential_provider_extension/password_spec_fetcher.h"
  5. #include "base/base64.h"
  6. #include "components/autofill/core/browser/proto/password_requirements.pb.h"
  7. #if !defined(__has_feature) || !__has_feature(objc_arc)
  8. #error "This file requires ARC support."
  9. #endif
  10. using autofill::DomainSuggestions;
  11. using autofill::PasswordRequirementsSpec;
  12. namespace {
  13. // URL of the fetching endpoint.
  14. NSString* const kPasswordSpecURL =
  15. @"https://content-autofill.googleapis.com/v1/domainSuggestions/";
  16. // Header field name for the API key.
  17. NSString* const kApiKeyHeaderField = @"X-Goog-Api-Key";
  18. // Encoding requested from the server.
  19. NSString* const kEncodeKeyValue = @"base64";
  20. // Header field name for the encoding.
  21. NSString* const kEncodeKeyHeaderField = @"X-Goog-Encode-Response-If-Executable";
  22. // Query parameter name to for the type of response.
  23. NSString* const kAltQueryName = @"alt";
  24. // Query parameter value for a bits response (compared to a JSON response).
  25. NSString* const kAltQueryValue = @"proto";
  26. // Timeout for the spec fetching request.
  27. const NSTimeInterval kPasswordSpecTimeout = 10;
  28. }
  29. @interface PasswordSpecFetcher ()
  30. // Host that identifies the spec to be fetched.
  31. @property(nonatomic, copy) NSString* host;
  32. // API key to query the spec.
  33. @property(nonatomic, copy) NSString* APIKey;
  34. // Data task for fetching the spec.
  35. @property(nonatomic, copy) NSURLSessionDataTask* task;
  36. // Completion to be called once there is a response.
  37. @property(nonatomic, copy) FetchSpecCompletionBlock completion;
  38. // The spec if ready or an empty one if fetch hasn't happened.
  39. @property(nonatomic, readwrite) PasswordRequirementsSpec spec;
  40. @end
  41. @implementation PasswordSpecFetcher
  42. - (instancetype)initWithHost:(NSString*)host APIKey:(NSString*)APIKey {
  43. self = [super init];
  44. if (self) {
  45. // Replace a nil host with the empty string.
  46. if (!host) {
  47. host = @"";
  48. }
  49. _host = [host stringByAddingPercentEncodingWithAllowedCharacters:
  50. NSCharacterSet.URLQueryAllowedCharacterSet];
  51. _APIKey = APIKey;
  52. }
  53. return self;
  54. }
  55. - (BOOL)didFetchSpec {
  56. return self.task.state == NSURLSessionTaskStateCompleted;
  57. }
  58. - (void)fetchSpecWithCompletion:(FetchSpecCompletionBlock)completion {
  59. self.completion = completion;
  60. if (self.task) {
  61. return;
  62. }
  63. NSString* finalURL = [kPasswordSpecURL stringByAppendingString:self.host];
  64. NSURLComponents* URLComponents =
  65. [NSURLComponents componentsWithString:finalURL];
  66. NSURLQueryItem* queryAltItem =
  67. [NSURLQueryItem queryItemWithName:kAltQueryName value:kAltQueryValue];
  68. URLComponents.queryItems = @[ queryAltItem ];
  69. NSMutableURLRequest* request =
  70. [NSMutableURLRequest requestWithURL:URLComponents.URL
  71. cachePolicy:NSURLRequestUseProtocolCachePolicy
  72. timeoutInterval:kPasswordSpecTimeout];
  73. [request setValue:self.APIKey forHTTPHeaderField:kApiKeyHeaderField];
  74. [request setValue:kEncodeKeyValue forHTTPHeaderField:kEncodeKeyHeaderField];
  75. __weak __typeof__(self) weakSelf = self;
  76. NSURLSession* session = [NSURLSession sharedSession];
  77. self.task =
  78. [session dataTaskWithRequest:request
  79. completionHandler:^(NSData* data, NSURLResponse* response,
  80. NSError* error) {
  81. [weakSelf onReceivedData:data response:response error:error];
  82. }];
  83. [self.task resume];
  84. }
  85. - (void)onReceivedData:(NSData*)data
  86. response:(NSURLResponse*)response
  87. error:(NSError*)error {
  88. // Return early if there is an error.
  89. if (error) {
  90. [self executeCompletion];
  91. return;
  92. }
  93. // Parse the proto and execute completion.
  94. std::string decoded;
  95. const base::StringPiece encoded_bytes(static_cast<const char*>([data bytes]),
  96. [data length]);
  97. if (base::Base64Decode(encoded_bytes, &decoded)) {
  98. DomainSuggestions suggestions;
  99. suggestions.ParseFromString(decoded);
  100. if (suggestions.has_password_requirements()) {
  101. self.spec = suggestions.password_requirements();
  102. }
  103. }
  104. [self executeCompletion];
  105. }
  106. // Executes the completion if present. And releases it after.
  107. - (void)executeCompletion {
  108. if (self.completion) {
  109. auto completion = self.completion;
  110. self.completion = nil;
  111. dispatch_async(dispatch_get_main_queue(), ^{
  112. completion(self.spec);
  113. });
  114. }
  115. }
  116. @end