| // Copyright 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/browser/passwords/password_controller.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <map> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/mac/foundation_util.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "components/autofill/core/common/password_form.h" |
| #include "components/autofill/core/common/password_form_fill_data.h" |
| #include "components/browser_sync/profile_sync_service.h" |
| #include "components/infobars/core/infobar_manager.h" |
| #include "components/password_manager/core/browser/password_bubble_experiment.h" |
| #include "components/password_manager/core/browser/password_generation_manager.h" |
| #include "components/password_manager/core/browser/password_manager.h" |
| #include "components/password_manager/core/browser/password_manager_client.h" |
| #include "components/password_manager/core/browser/password_manager_driver.h" |
| #include "components/sync/driver/sync_service.h" |
| #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| #include "ios/chrome/browser/experimental_flags.h" |
| #include "ios/chrome/browser/infobars/infobar_manager_impl.h" |
| #import "ios/chrome/browser/passwords/ios_chrome_save_password_infobar_delegate.h" |
| #import "ios/chrome/browser/passwords/ios_chrome_update_password_infobar_delegate.h" |
| #import "ios/chrome/browser/passwords/js_password_manager.h" |
| #import "ios/chrome/browser/passwords/password_generation_agent.h" |
| #include "ios/chrome/browser/sync/ios_chrome_profile_sync_service_factory.h" |
| #import "ios/web/public/origin_util.h" |
| #include "ios/web/public/url_scheme_util.h" |
| #import "ios/web/public/web_state/js/crw_js_injection_receiver.h" |
| #import "ios/web/public/web_state/web_state.h" |
| #include "url/gurl.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| using password_manager::PasswordFormManager; |
| using password_manager::PasswordGenerationManager; |
| using password_manager::PasswordManager; |
| using password_manager::PasswordManagerClient; |
| using password_manager::PasswordManagerDriver; |
| |
| namespace { |
| // Types of password infobars to display. |
| enum class PasswordInfoBarType { SAVE, UPDATE }; |
| } |
| |
| @interface PasswordController () |
| |
| // This is set to YES as soon as the associated WebState is destroyed. |
| @property(readonly) BOOL isWebStateDestroyed; |
| |
| // Accessor for property inside block. |
| @property(readonly) PasswordManager* passwordManager; |
| |
| @end |
| |
| @interface PasswordController ()<FormSuggestionProvider> |
| |
| // Parses the |jsonString| which contatins the password forms found on a web |
| // page to populate the |forms| vector. |
| - (void)getPasswordForms:(std::vector<autofill::PasswordForm>*)forms |
| fromFormsJSON:(NSString*)jsonString |
| pageURL:(const GURL&)pageURL; |
| |
| // Processes the JSON string returned as a result of extracting the submitted |
| // form data and populates |form|. Returns YES on success. NO otherwise. |
| // |form| cannot be nil. |
| - (BOOL)getPasswordForm:(autofill::PasswordForm*)form |
| fromPasswordFormJSON:(NSString*)jsonString |
| pageURL:(const GURL&)pageURL; |
| |
| // Informs the |passwordManager_| of the password forms (if any were present) |
| // that have been found on the page. |
| - (void)didFinishPasswordFormExtraction: |
| (const std::vector<autofill::PasswordForm>&)forms; |
| |
| // Autofills |username| and |password| into the form specified by |formData|, |
| // invoking |completionHandler| when finished with YES if successful and |
| // NO otherwise. |completionHandler| may be nil. |
| - (void)fillPasswordForm:(const autofill::PasswordFormFillData&)formData |
| withUsername:(const base::string16&)username |
| password:(const base::string16&)password |
| completionHandler:(void (^)(BOOL))completionHandler; |
| |
| // Uses JavaScript to find password forms. Calls |completionHandler| with the |
| // extracted information used for matching and saving passwords. Calls |
| // |completionHandler| with an empty vector if no password forms are found. |
| // |completionHandler| cannot be nil. |
| - (void)findPasswordFormsWithCompletionHandler: |
| (void (^)(const std::vector<autofill::PasswordForm>&))completionHandler; |
| |
| // Finds the currently submitted password form and calls |completionHandler| |
| // with the populated data structure. |found| is YES if the current form was |
| // found successfully, NO otherwise. |completionHandler| cannot be nil. |
| - (void)extractSubmittedPasswordForm:(const std::string&)formName |
| completionHandler: |
| (void (^)(BOOL found, |
| const autofill::PasswordForm& form)) |
| completionHandler; |
| |
| // Takes values from a JSON |dictionary| and populates the |form|. |
| // The |pageLocation| is the URL of the current page. |
| // Returns YES if the form was correctly populated, NO otherwise. |
| - (BOOL)getPasswordForm:(autofill::PasswordForm*)form |
| fromDictionary:(const base::DictionaryValue*)dictionary |
| pageURL:(const GURL&)pageLocation; |
| |
| // Displays infobar for |form| with |type|. If |type| is UPDATE, the user |
| // is prompted to update the password. If |type| is SAVE, the user is prompted |
| // to save the password. |
| - (void)showInfoBarForForm:(std::unique_ptr<PasswordFormManager>)form |
| infoBarType:(PasswordInfoBarType)type; |
| |
| @end |
| |
| namespace { |
| |
| // Constructs an array of FormSuggestions, each corresponding to a username/ |
| // password pair in |formFillData|, such that |prefix| is a prefix of the |
| // username of each suggestion. |
| NSArray* BuildSuggestions(const autofill::PasswordFormFillData& formFillData, |
| NSString* prefix) { |
| NSMutableArray* suggestions = [NSMutableArray array]; |
| |
| // Add the default credentials. |
| NSString* defaultUsername = |
| base::SysUTF16ToNSString(formFillData.username_field.value); |
| if ([prefix length] == 0 || |
| [defaultUsername rangeOfString:prefix].location == 0) { |
| NSString* origin = |
| formFillData.preferred_realm.empty() |
| ? nil |
| : base::SysUTF8ToNSString(formFillData.preferred_realm); |
| [suggestions addObject:[FormSuggestion suggestionWithValue:defaultUsername |
| displayDescription:origin |
| icon:nil |
| identifier:0]]; |
| } |
| |
| // Add the additional credentials. |
| for (const auto& it : formFillData.additional_logins) { |
| NSString* additionalUsername = base::SysUTF16ToNSString(it.first); |
| NSString* additionalOrigin = it.second.realm.empty() |
| ? nil |
| : base::SysUTF8ToNSString(it.second.realm); |
| if ([prefix length] == 0 || |
| [additionalUsername rangeOfString:prefix].location == 0) { |
| [suggestions |
| addObject:[FormSuggestion suggestionWithValue:additionalUsername |
| displayDescription:additionalOrigin |
| icon:nil |
| identifier:0]]; |
| } |
| } |
| |
| return suggestions; |
| } |
| |
| // Looks for a credential pair in |formData| for with |username|. If such a pair |
| // exists, returns true and |matchingPassword|; returns false otherwise. |
| bool FindMatchingUsername(const autofill::PasswordFormFillData& formData, |
| const base::string16& username, |
| base::string16* matchingPassword) { |
| base::string16 defaultUsername = formData.username_field.value; |
| if (defaultUsername == username) { |
| *matchingPassword = formData.password_field.value; |
| return true; |
| } |
| |
| // Check whether the user has finished typing an alternate username. |
| for (const auto& it : formData.additional_logins) { |
| if (it.first == username) { |
| *matchingPassword = it.second.password; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // Removes URL components not essential for matching the URL to |
| // saved password databases entries. The stripped components are: |
| // user, password, query and ref. |
| GURL stripURL(GURL& url) { |
| url::Replacements<char> replacements; |
| replacements.ClearUsername(); |
| replacements.ClearPassword(); |
| replacements.ClearQuery(); |
| replacements.ClearRef(); |
| return url.ReplaceComponents(replacements); |
| } |
| |
| // Serializes |formData| into a JSON string that can be used by the JS side |
| // of PasswordController. |
| NSString* SerializePasswordFormFillData( |
| const autofill::PasswordFormFillData& formData) { |
| // Repackage PasswordFormFillData as a JSON object. |
| base::DictionaryValue rootDict; |
| |
| // The normalized URL of the web page. |
| rootDict.SetString("origin", formData.origin.spec()); |
| |
| // The normalized URL from the "action" attribute of the <form> tag. |
| rootDict.SetString("action", formData.action.spec()); |
| |
| // Input elements in the form. The list does not necessarily contain |
| // all elements from the form, but all elements listed here are required |
| // to identify the right form to fill. |
| auto fieldList = base::MakeUnique<base::ListValue>(); |
| |
| auto usernameField = base::MakeUnique<base::DictionaryValue>(); |
| usernameField->SetString("name", formData.username_field.name); |
| usernameField->SetString("value", formData.username_field.value); |
| fieldList->Append(std::move(usernameField)); |
| |
| auto passwordField = base::MakeUnique<base::DictionaryValue>(); |
| passwordField->SetString("name", formData.password_field.name); |
| passwordField->SetString("value", formData.password_field.value); |
| fieldList->Append(std::move(passwordField)); |
| |
| rootDict.Set("fields", std::move(fieldList)); |
| |
| std::string jsonString; |
| base::JSONWriter::Write(rootDict, &jsonString); |
| return base::SysUTF8ToNSString(jsonString); |
| } |
| |
| // Returns true if the trust level for the current page URL of |web_state| is |
| // kAbsolute. If |page_url| is not null, fills it with the current page URL. |
| bool GetPageURLAndCheckTrustLevel(web::WebState* web_state, GURL* page_url) { |
| auto trustLevel = web::URLVerificationTrustLevel::kNone; |
| GURL dummy; |
| if (!page_url) |
| page_url = &dummy; |
| *page_url = web_state->GetCurrentURL(&trustLevel); |
| return trustLevel == web::URLVerificationTrustLevel::kAbsolute; |
| } |
| |
| } // namespace |
| |
| @implementation PasswordController { |
| std::unique_ptr<PasswordManager> passwordManager_; |
| std::unique_ptr<PasswordGenerationManager> passwordGenerationManager_; |
| std::unique_ptr<PasswordManagerClient> passwordManagerClient_; |
| std::unique_ptr<PasswordManagerDriver> passwordManagerDriver_; |
| PasswordGenerationAgent* passwordGenerationAgent_; |
| |
| JsPasswordManager* passwordJsManager_; // weak |
| web::WebState* webState_; // weak |
| |
| // The pending form data. |
| std::unique_ptr<autofill::PasswordFormFillData> formData_; |
| |
| // Bridge to observe WebState from Objective-C. |
| std::unique_ptr<web::WebStateObserverBridge> webStateObserverBridge_; |
| } |
| |
| @synthesize isWebStateDestroyed = isWebStateDestroyed_; |
| |
| - (instancetype)initWithWebState:(web::WebState*)webState |
| passwordsUiDelegate:(id<PasswordsUiDelegate>)UIDelegate { |
| self = [self initWithWebState:webState |
| passwordsUiDelegate:UIDelegate |
| client:nullptr]; |
| return self; |
| } |
| |
| - (instancetype)initWithWebState:(web::WebState*)webState |
| passwordsUiDelegate:(id<PasswordsUiDelegate>)UIDelegate |
| client:(std::unique_ptr<PasswordManagerClient>) |
| passwordManagerClient { |
| DCHECK(webState); |
| self = [super init]; |
| if (self) { |
| webState_ = webState; |
| if (passwordManagerClient) |
| passwordManagerClient_ = std::move(passwordManagerClient); |
| else |
| passwordManagerClient_.reset(new IOSChromePasswordManagerClient(self)); |
| passwordManager_.reset(new PasswordManager(passwordManagerClient_.get())); |
| passwordManagerDriver_.reset(new IOSChromePasswordManagerDriver(self)); |
| if (experimental_flags::IsPasswordGenerationEnabled() && |
| !passwordManagerClient_->IsIncognito()) { |
| passwordGenerationManager_.reset(new PasswordGenerationManager( |
| passwordManagerClient_.get(), passwordManagerDriver_.get())); |
| passwordGenerationAgent_ = [[PasswordGenerationAgent alloc] |
| initWithWebState:webState |
| passwordManager:passwordManager_.get() |
| passwordManagerDriver:passwordManagerDriver_.get() |
| passwordsUiDelegate:UIDelegate]; |
| } |
| |
| passwordJsManager_ = base::mac::ObjCCastStrict<JsPasswordManager>( |
| [webState->GetJSInjectionReceiver() |
| instanceOfClass:[JsPasswordManager class]]); |
| webStateObserverBridge_.reset( |
| new web::WebStateObserverBridge(webState, self)); |
| } |
| return self; |
| } |
| |
| - (instancetype)init { |
| NOTREACHED(); |
| return nil; |
| } |
| |
| - (void)dealloc { |
| [self detach]; |
| } |
| |
| - (ios::ChromeBrowserState*)browserState { |
| return webState_ ? ios::ChromeBrowserState::FromBrowserState( |
| webState_->GetBrowserState()) |
| : nullptr; |
| } |
| |
| - (const GURL&)lastCommittedURL { |
| if (!webStateObserverBridge_ || !webStateObserverBridge_->web_state()) |
| return GURL::EmptyGURL(); |
| return webStateObserverBridge_->web_state()->GetLastCommittedURL(); |
| } |
| |
| - (void)detach { |
| webState_ = nullptr; |
| webStateObserverBridge_.reset(); |
| passwordGenerationAgent_ = nil; |
| passwordGenerationManager_.reset(); |
| passwordManagerDriver_.reset(); |
| passwordManager_.reset(); |
| passwordManagerClient_.reset(); |
| } |
| |
| - (void)findAndFillPasswordForms:(NSString*)username |
| password:(NSString*)password |
| completionHandler:(void (^)(BOOL))completionHandler { |
| [self findPasswordFormsWithCompletionHandler:^( |
| const std::vector<autofill::PasswordForm>& forms) { |
| for (const auto& form : forms) { |
| autofill::PasswordFormFillData formData; |
| std::map<base::string16, const autofill::PasswordForm*> matches; |
| // Initialize |matches| to satisfy the expectation from |
| // InitPasswordFormFillData() that the preferred match (3rd parameter) |
| // should be one of the |matches|. |
| matches.insert(std::make_pair(form.username_value, &form)); |
| autofill::InitPasswordFormFillData(form, matches, &form, false, false, |
| &formData); |
| [self fillPasswordForm:formData |
| withUsername:base::SysNSStringToUTF16(username) |
| password:base::SysNSStringToUTF16(password) |
| completionHandler:completionHandler]; |
| } |
| }]; |
| } |
| |
| #pragma mark - |
| #pragma mark CRWWebStateObserver |
| |
| - (void)webState:(web::WebState*)webState didLoadPageWithSuccess:(BOOL)success { |
| // Clear per-page state. |
| formData_.reset(); |
| |
| // Retrieve the identity of the page. In case the page might be malicous, |
| // returns early. |
| GURL pageURL; |
| if (!GetPageURLAndCheckTrustLevel(webState, &pageURL)) |
| return; |
| |
| if (!web::UrlHasWebScheme(pageURL)) |
| return; |
| |
| // Notify the password manager that the page loaded so it can clear its own |
| // per-page state. |
| passwordManager_->DidNavigateMainFrame(); |
| |
| if (!webState->ContentIsHTML()) { |
| // If the current page is not HTML, it does not contain any HTML forms. |
| [self |
| didFinishPasswordFormExtraction:std::vector<autofill::PasswordForm>()]; |
| } |
| |
| // Read all password forms from the page and send them to the password |
| // manager. |
| __weak PasswordController* weakSelf = self; |
| [self findPasswordFormsWithCompletionHandler:^( |
| const std::vector<autofill::PasswordForm>& forms) { |
| [weakSelf didFinishPasswordFormExtraction:forms]; |
| }]; |
| } |
| |
| - (void)webState:(web::WebState*)webState |
| didSubmitDocumentWithFormNamed:(const std::string&)formName |
| userInitiated:(BOOL)userInitiated { |
| __weak PasswordController* weakSelf = self; |
| // This code is racing against the new page loading and will not get the |
| // password form data if the page has changed. In most cases this code wins |
| // the race. |
| // TODO(crbug.com/418827): Fix this by passing in more data from the JS side. |
| id completionHandler = ^(BOOL found, const autofill::PasswordForm& form) { |
| PasswordController* strongSelf = weakSelf; |
| if (strongSelf && ![strongSelf isWebStateDestroyed]) { |
| strongSelf.passwordManager->OnPasswordFormSubmitted( |
| strongSelf.passwordManagerDriver, form); |
| } |
| }; |
| [self extractSubmittedPasswordForm:formName |
| completionHandler:completionHandler]; |
| } |
| |
| - (void)webStateDestroyed:(web::WebState*)webState { |
| isWebStateDestroyed_ = YES; |
| [self detach]; |
| } |
| |
| - (void)findPasswordFormsWithCompletionHandler: |
| (void (^)(const std::vector<autofill::PasswordForm>&))completionHandler { |
| DCHECK(completionHandler); |
| |
| if (!webStateObserverBridge_ || !webStateObserverBridge_->web_state()) |
| return; |
| |
| GURL pageURL; |
| if (!GetPageURLAndCheckTrustLevel(webStateObserverBridge_->web_state(), |
| &pageURL)) { |
| return; |
| } |
| |
| __weak PasswordController* weakSelf = self; |
| [passwordJsManager_ findPasswordFormsWithCompletionHandler:^( |
| NSString* jsonString) { |
| std::vector<autofill::PasswordForm> forms; |
| [weakSelf getPasswordForms:&forms fromFormsJSON:jsonString pageURL:pageURL]; |
| completionHandler(forms); |
| }]; |
| } |
| |
| - (void)getPasswordForms:(std::vector<autofill::PasswordForm>*)forms |
| fromFormsJSON:(NSString*)JSONNSString |
| pageURL:(const GURL&)pageURL { |
| DCHECK(forms); |
| std::string JSONString = base::SysNSStringToUTF8(JSONNSString); |
| if (JSONString.empty()) { |
| VLOG(1) << "Error in password controller javascript."; |
| return; |
| } |
| |
| int errorCode = 0; |
| std::string errorMessage; |
| std::unique_ptr<base::Value> jsonData(base::JSONReader::ReadAndReturnError( |
| JSONString, false, &errorCode, &errorMessage)); |
| if (errorCode || !jsonData || !jsonData->IsType(base::Value::Type::LIST)) { |
| VLOG(1) << "JSON parse error " << errorMessage |
| << " JSON string: " << JSONString; |
| return; |
| } |
| |
| const base::ListValue* formDataList; |
| if (!jsonData->GetAsList(&formDataList)) |
| return; |
| for (size_t i = 0; i != formDataList->GetSize(); ++i) { |
| const base::DictionaryValue* formData; |
| if (formDataList->GetDictionary(i, &formData)) { |
| autofill::PasswordForm form; |
| if ([self getPasswordForm:&form |
| fromDictionary:formData |
| pageURL:pageURL]) { |
| forms->push_back(form); |
| } |
| } |
| } |
| } |
| |
| - (void)extractSubmittedPasswordForm:(const std::string&)formName |
| completionHandler: |
| (void (^)(BOOL found, |
| const autofill::PasswordForm& form)) |
| completionHandler { |
| DCHECK(completionHandler); |
| |
| if (!webStateObserverBridge_ || !webStateObserverBridge_->web_state()) |
| return; |
| |
| GURL pageURL; |
| if (!GetPageURLAndCheckTrustLevel(webStateObserverBridge_->web_state(), |
| &pageURL)) { |
| completionHandler(NO, autofill::PasswordForm()); |
| return; |
| } |
| |
| __weak PasswordController* weakSelf = self; |
| id extractSubmittedFormCompletionHandler = ^(NSString* jsonString) { |
| autofill::PasswordForm form; |
| BOOL found = [weakSelf getPasswordForm:&form |
| fromPasswordFormJSON:jsonString |
| pageURL:pageURL]; |
| completionHandler(found, form); |
| }; |
| |
| [passwordJsManager_ extractForm:base::SysUTF8ToNSString(formName) |
| completionHandler:extractSubmittedFormCompletionHandler]; |
| } |
| |
| - (BOOL)getPasswordForm:(autofill::PasswordForm*)form |
| fromPasswordFormJSON:(NSString*)JSONNSString |
| pageURL:(const GURL&)pageURL { |
| DCHECK(form); |
| // There is no identifiable password form on the page. |
| if ([JSONNSString isEqualToString:@"noPasswordsFound"]) |
| return NO; |
| |
| int errorCode = 0; |
| std::string errorMessage; |
| std::string JSONString = base::SysNSStringToUTF8(JSONNSString); |
| std::unique_ptr<const base::Value> JSONData( |
| base::JSONReader::ReadAndReturnError(JSONString, false, &errorCode, |
| &errorMessage)); |
| |
| // If the the JSON string contains null, there is no identifiable password |
| // form on the page. |
| if (!errorCode && !JSONData) { |
| return NO; |
| } |
| |
| if (errorCode || !JSONData->IsType(base::Value::Type::DICTIONARY)) { |
| VLOG(1) << "JSON parse error " << errorMessage |
| << " JSON string: " << JSONString; |
| return NO; |
| } |
| |
| const base::DictionaryValue* passwordJsonData; |
| return JSONData->GetAsDictionary(&passwordJsonData) && |
| [self getPasswordForm:form |
| fromDictionary:passwordJsonData |
| pageURL:pageURL]; |
| } |
| |
| - (void)didFinishPasswordFormExtraction: |
| (const std::vector<autofill::PasswordForm>&)forms { |
| // Do nothing if |self| has been detached. |
| if (!passwordManager_) |
| return; |
| |
| if (!forms.empty()) { |
| // Notify web_state about password forms, so that this can be taken into |
| // account for the security state. |
| if (webStateObserverBridge_) { |
| web::WebState* web_state = webStateObserverBridge_->web_state(); |
| if (web_state && !web::IsOriginSecure(web_state->GetLastCommittedURL())) { |
| web_state->OnPasswordInputShownOnHttp(); |
| } |
| } |
| |
| // Invoke the password manager callback to autofill password forms |
| // on the loaded page. |
| passwordManager_->OnPasswordFormsParsed(passwordManagerDriver_.get(), |
| forms); |
| |
| // Pass the forms to PasswordGenerationAgent to look for account creation |
| // forms with local heuristics. |
| [passwordGenerationAgent_ processParsedPasswordForms:forms]; |
| } |
| // Invoke the password manager callback to check if password was |
| // accepted or rejected. If accepted, infobar is presented. If |
| // rejected, the provisionally saved password is deleted. On Chrome |
| // w/ a renderer, it is the renderer who calls OnPasswordFormsParsed() |
| // and OnPasswordFormsRendered(). Bling has to improvised a bit on the |
| // ordering of these two calls. |
| passwordManager_->OnPasswordFormsRendered(passwordManagerDriver_.get(), forms, |
| true); |
| } |
| |
| - (id<FormInputAccessoryViewProvider>)accessoryViewProvider { |
| return [passwordGenerationAgent_ accessoryViewProvider]; |
| } |
| |
| #pragma mark - |
| #pragma mark FormSuggestionProvider |
| |
| - (id<FormSuggestionProvider>)suggestionProvider { |
| return self; |
| } |
| |
| - (void)checkIfSuggestionsAvailableForForm:(NSString*)formName |
| field:(NSString*)fieldName |
| type:(NSString*)type |
| typedValue:(NSString*)typedValue |
| webState:(web::WebState*)webState |
| completionHandler: |
| (SuggestionsAvailableCompletion)completion { |
| if (!formData_ || !GetPageURLAndCheckTrustLevel(webState, nullptr)) { |
| completion(NO); |
| return; |
| } |
| |
| // Suggestions are available for the username field of the password form. |
| const base::string16& pendingFormName = formData_->name; |
| const base::string16& pendingFieldName = formData_->username_field.name; |
| completion(base::SysNSStringToUTF16(formName) == pendingFormName && |
| base::SysNSStringToUTF16(fieldName) == pendingFieldName); |
| } |
| |
| - (void)retrieveSuggestionsForForm:(NSString*)formName |
| field:(NSString*)fieldName |
| type:(NSString*)type |
| typedValue:(NSString*)typedValue |
| webState:(web::WebState*)webState |
| completionHandler:(SuggestionsReadyCompletion)completion { |
| DCHECK(GetPageURLAndCheckTrustLevel(webState, nullptr)); |
| if (!formData_) { |
| completion(@[], nil); |
| return; |
| } |
| completion(BuildSuggestions(*formData_, typedValue), self); |
| } |
| |
| - (void)didSelectSuggestion:(FormSuggestion*)suggestion |
| forField:(NSString*)fieldName |
| form:(NSString*)formName |
| completionHandler:(SuggestionHandledCompletion)completion { |
| const base::string16 username = base::SysNSStringToUTF16(suggestion.value); |
| base::string16 password; |
| if (FindMatchingUsername(*formData_, username, &password)) { |
| [self fillPasswordForm:*formData_ |
| withUsername:username |
| password:password |
| completionHandler:^(BOOL success) { |
| completion(); |
| }]; |
| } else { |
| completion(); |
| } |
| } |
| |
| #pragma mark - PasswordManagerClientDelegate |
| |
| - (void)showSavePasswordInfoBar: |
| (std::unique_ptr<PasswordFormManager>)formToSave { |
| [self showInfoBarForForm:std::move(formToSave) |
| infoBarType:PasswordInfoBarType::SAVE]; |
| } |
| |
| - (void)showUpdatePasswordInfoBar: |
| (std::unique_ptr<PasswordFormManager>)formToUpdate { |
| [self showInfoBarForForm:std::move(formToUpdate) |
| infoBarType:PasswordInfoBarType::UPDATE]; |
| } |
| |
| #pragma mark - |
| #pragma mark WebPasswordFormData Adaptation |
| |
| - (BOOL)getPasswordForm:(autofill::PasswordForm*)form |
| fromDictionary:(const base::DictionaryValue*)dictionary |
| pageURL:(const GURL&)pageLocation { |
| DCHECK(form); |
| DCHECK(dictionary); |
| DCHECK(pageLocation.is_valid()); |
| |
| base::string16 formOrigin; |
| if (!(dictionary->GetString("origin", &formOrigin)) || |
| (GURL(formOrigin).GetOrigin() != pageLocation.GetOrigin())) { |
| return NO; |
| } |
| |
| std::string origin = pageLocation.spec(); |
| GURL originUrl(origin); |
| if (!originUrl.is_valid()) { |
| return NO; |
| } |
| form->origin = stripURL(originUrl); |
| url::Replacements<char> remove_path; |
| remove_path.ClearPath(); |
| form->signon_realm = form->origin.ReplaceComponents(remove_path).spec(); |
| |
| std::string action; |
| // Not checking the return value, as empty action is valid. |
| dictionary->GetString("action", &action); |
| GURL actionUrl = action.empty() ? originUrl : originUrl.Resolve(action); |
| if (!actionUrl.is_valid()) |
| return NO; |
| form->action = stripURL(actionUrl); |
| |
| if (!dictionary->GetString("usernameElement", &form->username_element) || |
| !dictionary->GetString("usernameValue", &form->username_value) || |
| !dictionary->GetString("name", &form->form_data.name)) { |
| return NO; |
| } |
| |
| const base::ListValue* passwordDataList; |
| if (!dictionary->GetList("passwords", &passwordDataList)) |
| return NO; |
| |
| size_t passwordCount = passwordDataList->GetSize(); |
| if (passwordCount == 0) |
| return NO; |
| |
| const base::DictionaryValue* passwordData; |
| |
| // The "passwords" list contains element/value pairs for |
| // password inputs found in the form. We recognize |
| // up to three passwords in any given form and ignore the rest. |
| const size_t kMaxPasswordsConsidered = 3; |
| base::string16 elements[kMaxPasswordsConsidered]; |
| base::string16 values[kMaxPasswordsConsidered]; |
| |
| for (size_t i = 0; i < std::min(passwordCount, kMaxPasswordsConsidered); |
| ++i) { |
| if (!passwordDataList->GetDictionary(i, &passwordData) || |
| !passwordData->GetString("element", &elements[i]) || |
| !passwordData->GetString("value", &values[i])) { |
| return NO; |
| } |
| } |
| |
| // Determine which password is the main one, and which is |
| // an old password (e.g on a "make new password" form), if any. |
| // TODO(crbug.com/564578): Make this compatible with other platforms. |
| switch (passwordCount) { |
| case 1: |
| form->password_element = elements[0]; |
| form->password_value = values[0]; |
| break; |
| case 2: { |
| if (!values[0].empty() && values[0] == values[1]) { |
| // Treat two identical passwords as a single password new password, with |
| // confirmation. This can be either be a sign-up form or a password |
| // change form that does not ask for a new password. |
| form->new_password_element = elements[0]; |
| form->new_password_value = values[0]; |
| } else { |
| // Assume first is old password, second is new (no choice but to guess). |
| form->password_element = elements[0]; |
| form->password_value = values[0]; |
| form->new_password_element = elements[1]; |
| form->new_password_value = values[1]; |
| } |
| break; |
| default: |
| if (!values[0].empty() && values[0] == values[1] && |
| values[0] == values[2]) { |
| // All three passwords the same? This does not make sense, do not |
| // add the password element. |
| break; |
| } else if (values[0] == values[1]) { |
| // First two the same and the third different implies that the old |
| // password is the duplicated one. |
| form->password_element = elements[0]; |
| form->password_value = values[0]; |
| form->new_password_element = elements[2]; |
| form->new_password_value = values[2]; |
| } else if (values[1] == values[2]) { |
| // Two the same and one different -> new password is the duplicated |
| // one. |
| form->password_element = elements[0]; |
| form->password_value = values[0]; |
| form->new_password_element = elements[1]; |
| form->new_password_value = values[1]; |
| } else { |
| // Three different passwords, or first and last match with middle |
| // different. No idea which is which, so no luck. |
| } |
| break; |
| } |
| } |
| |
| // Fill in as much data about the fields as is required for password |
| // generation. |
| const base::ListValue* fieldList = nullptr; |
| if (!dictionary->GetList("fields", &fieldList)) |
| return NO; |
| for (size_t i = 0; i < fieldList->GetSize(); ++i) { |
| const base::DictionaryValue* fieldDictionary = nullptr; |
| if (!fieldList->GetDictionary(i, &fieldDictionary)) |
| return NO; |
| base::string16 element; |
| base::string16 type; |
| if (!fieldDictionary->GetString("element", &element) || |
| !fieldDictionary->GetString("type", &type)) { |
| return NO; |
| } |
| autofill::FormFieldData field; |
| field.name = std::move(element); |
| field.form_control_type = base::UTF16ToUTF8(type); |
| form->form_data.fields.push_back(field); |
| } |
| |
| return YES; |
| } |
| |
| - (void)fillPasswordForm:(const autofill::PasswordFormFillData&)formData |
| withUsername:(const base::string16&)username |
| password:(const base::string16&)password |
| completionHandler:(void (^)(BOOL))completionHandler { |
| // Send JSON over to the web view. |
| [passwordJsManager_ fillPasswordForm:SerializePasswordFormFillData(formData) |
| withUsername:base::SysUTF16ToNSString(username) |
| password:base::SysUTF16ToNSString(password) |
| completionHandler:^(BOOL result) { |
| if (completionHandler) |
| completionHandler(result); |
| }]; |
| } |
| |
| - (void)fillPasswordForm:(const autofill::PasswordFormFillData&)formData |
| completionHandler:(void (^)(BOOL))completionHandler { |
| formData_.reset(new autofill::PasswordFormFillData(formData)); |
| |
| // Don't fill immediately if waiting for the user to type a username. |
| if (formData_->wait_for_username) { |
| if (completionHandler) |
| completionHandler(NO); |
| return; |
| } |
| |
| [self fillPasswordForm:*formData_ |
| withUsername:formData_->username_field.value |
| password:formData_->password_field.value |
| completionHandler:completionHandler]; |
| } |
| |
| - (PasswordGenerationAgent*)passwordGenerationAgent { |
| return passwordGenerationAgent_; |
| } |
| |
| - (PasswordGenerationManager*)passwordGenerationManager { |
| return passwordGenerationManager_.get(); |
| } |
| |
| - (PasswordManagerClient*)passwordManagerClient { |
| return passwordManagerClient_.get(); |
| } |
| |
| - (PasswordManagerDriver*)passwordManagerDriver { |
| return passwordManagerDriver_.get(); |
| } |
| |
| - (PasswordManager*)passwordManager { |
| return passwordManager_.get(); |
| } |
| |
| - (JsPasswordManager*)passwordJsManager { |
| return passwordJsManager_; |
| } |
| |
| #pragma mark - Private methods |
| |
| - (void)showInfoBarForForm:(std::unique_ptr<PasswordFormManager>)form |
| infoBarType:(PasswordInfoBarType)type { |
| if (!webStateObserverBridge_ || !webStateObserverBridge_->web_state()) |
| return; |
| |
| bool isSmartLockBrandingEnabled = false; |
| if (self.browserState) { |
| syncer::SyncService* sync_service = |
| IOSChromeProfileSyncServiceFactory::GetForBrowserState( |
| self.browserState); |
| isSmartLockBrandingEnabled = |
| password_bubble_experiment::IsSmartLockUser(sync_service); |
| } |
| infobars::InfoBarManager* infoBarManager = |
| InfoBarManagerImpl::FromWebState(webStateObserverBridge_->web_state()); |
| |
| switch (type) { |
| case PasswordInfoBarType::SAVE: |
| IOSChromeSavePasswordInfoBarDelegate::Create( |
| isSmartLockBrandingEnabled, infoBarManager, std::move(form)); |
| break; |
| |
| case PasswordInfoBarType::UPDATE: |
| IOSChromeUpdatePasswordInfoBarDelegate::Create( |
| isSmartLockBrandingEnabled, infoBarManager, std::move(form)); |
| break; |
| } |
| } |
| |
| @end |