summaryrefslogtreecommitdiffstats
path: root/ios/chrome/browser/passwords/credential_manager.mm
diff options
context:
space:
mode:
Diffstat (limited to 'ios/chrome/browser/passwords/credential_manager.mm')
-rw-r--r--ios/chrome/browser/passwords/credential_manager.mm360
1 files changed, 360 insertions, 0 deletions
diff --git a/ios/chrome/browser/passwords/credential_manager.mm b/ios/chrome/browser/passwords/credential_manager.mm
new file mode 100644
index 0000000..405e22f
--- /dev/null
+++ b/ios/chrome/browser/passwords/credential_manager.mm
@@ -0,0 +1,360 @@
+// Copyright 2015 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/credential_manager.h"
+
+#include "base/ios/ios_util.h"
+#import "base/ios/weak_nsobject.h"
+#include "base/mac/bind_objc_block.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/sys_string_conversions.h"
+#include "components/password_manager/core/browser/password_store_consumer.h"
+#include "components/password_manager/core/common/credential_manager_types.h"
+#include "components/password_manager/core/common/password_manager_pref_names.h"
+#import "ios/chrome/browser/passwords/js_credential_manager.h"
+#import "ios/web/public/url_scheme_util.h"
+#include "ios/web/public/web_state/credential.h"
+#include "ios/web/public/web_state/url_verification_constants.h"
+#include "ios/web/public/web_state/web_state.h"
+
+namespace {
+
+// Converts a password_manager::CredentialInfo to a web::Credential.
+web::Credential WebCredentialFromCredentialInfo(
+ const password_manager::CredentialInfo& credential_info) {
+ web::Credential credential;
+ switch (credential_info.type) {
+ case password_manager::CredentialType::CREDENTIAL_TYPE_EMPTY:
+ credential.type = web::CredentialType::CREDENTIAL_TYPE_EMPTY;
+ break;
+ case password_manager::CredentialType::CREDENTIAL_TYPE_PASSWORD:
+ credential.type = web::CredentialType::CREDENTIAL_TYPE_PASSWORD;
+ break;
+ case password_manager::CredentialType::CREDENTIAL_TYPE_FEDERATED:
+ credential.type = web::CredentialType::CREDENTIAL_TYPE_FEDERATED;
+ break;
+ }
+ credential.id = credential_info.id;
+ credential.name = credential_info.name;
+ credential.avatar_url = credential_info.icon;
+ credential.password = credential_info.password;
+ credential.federation_url = credential_info.federation;
+ return credential;
+}
+
+// Converts a web::Credential to a password_manager::CredentialInfo.
+password_manager::CredentialInfo CredentialInfoFromWebCredential(
+ const web::Credential& credential) {
+ password_manager::CredentialInfo credential_info;
+ switch (credential.type) {
+ case web::CredentialType::CREDENTIAL_TYPE_EMPTY:
+ credential_info.type =
+ password_manager::CredentialType::CREDENTIAL_TYPE_EMPTY;
+ break;
+ case web::CredentialType::CREDENTIAL_TYPE_PASSWORD:
+ credential_info.type =
+ password_manager::CredentialType::CREDENTIAL_TYPE_PASSWORD;
+ break;
+ case web::CredentialType::CREDENTIAL_TYPE_FEDERATED:
+ credential_info.type =
+ password_manager::CredentialType::CREDENTIAL_TYPE_FEDERATED;
+ break;
+ }
+ credential_info.id = credential.id;
+ credential_info.name = credential.name;
+ credential_info.icon = credential.avatar_url;
+ credential_info.password = credential.password;
+ credential_info.federation = credential.federation_url;
+ return credential_info;
+}
+
+} // namespace
+
+CredentialManager::CredentialManager(
+ web::WebState* web_state,
+ password_manager::PasswordManagerClient* client,
+ password_manager::PasswordManagerDriver* driver,
+ JSCredentialManager* js_manager)
+ : web::WebStateObserver(web_state),
+ pending_request_(nullptr),
+ form_manager_(nullptr),
+ js_manager_([js_manager retain]),
+ client_(client),
+ driver_(driver),
+ weak_factory_(this) {
+ zero_click_sign_in_enabled_.Init(
+ password_manager::prefs::kPasswordManagerAutoSignin, client_->GetPrefs());
+}
+
+CredentialManager::~CredentialManager() = default;
+
+void CredentialManager::PageLoaded(
+ web::PageLoadCompletionStatus load_completion_status) {
+ // Ensure the JavaScript is loaded when the page finishes loading.
+ web::URLVerificationTrustLevel trust_level =
+ web::URLVerificationTrustLevel::kNone;
+ const GURL page_url(web_state()->GetCurrentURL(&trust_level));
+ if (!base::ios::IsRunningOnIOS8OrLater() ||
+ trust_level != web::URLVerificationTrustLevel::kAbsolute ||
+ !web::UrlHasWebScheme(page_url) || !web_state()->ContentIsHTML()) {
+ return;
+ }
+ [js_manager_ inject];
+}
+
+void CredentialManager::CredentialsRequested(
+ int request_id,
+ const GURL& source_url,
+ bool zero_click_only,
+ const std::vector<std::string>& federations,
+ bool is_user_initiated) {
+ // Invoked when the page invokes navigator.credentials.request(), this
+ // function will attempt to retrieve a Credential from the PasswordStore that
+ // meets the specified parameters and, if successful, send it back to the page
+ // via SendCredential.
+ DCHECK_GE(request_id, 0);
+ password_manager::PasswordStore* store = GetPasswordStore();
+
+ // If there's an outstanding request, or the PasswordStore isn't loaded yet,
+ // the request should fail outright and the JS Promise should be rejected
+ // with an appropriate error.
+ if (pending_request_ || !store) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&CredentialManager::RejectPromise,
+ weak_factory_.GetWeakPtr(), request_id,
+ pending_request_ ? ERROR_TYPE_PENDING_REQUEST
+ : ERROR_TYPE_PASSWORD_STORE_UNAVAILABLE));
+ return;
+ }
+
+ // If the page requested a zero-click credential -- one that can be returned
+ // without first asking the user -- and if zero-click isn't currently
+ // available, send back an empty credential.
+ if (zero_click_only && !IsZeroClickAllowed()) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&CredentialManager::SendCredential,
+ weak_factory_.GetWeakPtr(), request_id,
+ password_manager::CredentialInfo()));
+ return;
+ }
+
+ // If the page origin is untrusted, the request should be rejected.
+ GURL page_url;
+ if (!GetUrlWithAbsoluteTrust(&page_url)) {
+ RejectPromise(request_id, ERROR_TYPE_SECURITY_ERROR_UNTRUSTED_ORIGIN);
+ return;
+ }
+
+ // Bundle up the arguments and forward them to the PasswordStore, which will
+ // asynchronously return the resulting Credential by invoking
+ // |SendCredential|.
+ std::vector<GURL> federation_urls;
+ for (const auto& federation : federations)
+ federation_urls.push_back(GURL(federation));
+ std::vector<std::string> realms;
+ pending_request_.reset(
+ new password_manager::CredentialManagerPendingRequestTask(
+ this, request_id, zero_click_only, page_url, federation_urls,
+ realms));
+ store->GetAutofillableLogins(pending_request_.get());
+}
+
+void CredentialManager::SignedIn(int request_id,
+ const GURL& source_url,
+ const web::Credential& credential) {
+ // Invoked when the page invokes navigator.credentials.notifySignedIn(), this
+ // function stores the signed-in |credential| and sends a message back to the
+ // page to resolve the Promise associated with |request_id|.
+ DCHECK(credential.type != web::CredentialType::CREDENTIAL_TYPE_EMPTY);
+ DCHECK_GE(request_id, 0);
+
+ // Requests from untrusted origins should be rejected.
+ GURL page_url;
+ if (!GetUrlWithAbsoluteTrust(&page_url)) {
+ RejectPromise(request_id, ERROR_TYPE_SECURITY_ERROR_UNTRUSTED_ORIGIN);
+ return;
+ }
+
+ // Notify the page that the notification was successful to avoid blocking the
+ // page while storing the credential. This is okay because the notification
+ // doesn't imply that the credential will be stored, just that it might be.
+ // It isn't the page's concern to know whether the storage took place or not.
+ ResolvePromise(request_id);
+
+ // Do nothing if the password manager isn't active.
+ if (!client_->IsSavingAndFillingEnabledForCurrentPage())
+ return;
+
+ // Store the signed-in credential so that the user can save it, if desired.
+ // Prompting the user and saving are handled by the PasswordFormManager.
+ scoped_ptr<autofill::PasswordForm> form(
+ password_manager::CreatePasswordFormFromCredentialInfo(
+ CredentialInfoFromWebCredential(credential), page_url));
+ form->skip_zero_click = !IsZeroClickAllowed();
+
+ // TODO(mkwst): This is a stub; we should be checking the PasswordStore to
+ // determine whether or not the credential exists, and calling UpdateLogin
+ // accordingly.
+ form_manager_.reset(
+ new password_manager::CredentialManagerPasswordFormManager(
+ client_, driver_->AsWeakPtr(), *form, this));
+}
+
+void CredentialManager::SignedOut(int request_id, const GURL& source_url) {
+ // Invoked when the page invokes navigator.credentials.notifySignedOut, this
+ // function notifies the PasswordStore that zero-click sign-in should be
+ // disabled for the current page origin.
+ DCHECK_GE(request_id, 0);
+
+ // Requests from untrusted origins should be rejected.
+ GURL page_url;
+ if (!GetUrlWithAbsoluteTrust(&page_url)) {
+ RejectPromise(request_id, ERROR_TYPE_SECURITY_ERROR_UNTRUSTED_ORIGIN);
+ return;
+ }
+
+ // The user signed out of the current page, so future zero-click credential
+ // requests for this page should fail: otherwise, the next time the user
+ // visits the page, if zero-click requests succeeded, the user might be auto-
+ // signed-in again with the credential that they just signed out. Forward this
+ // information to the PasswordStore via an asynchronous task.
+ password_manager::PasswordStore* store = GetPasswordStore();
+ if (store) {
+ // Bundle the origins that are sent to the PasswordStore if the task hasn't
+ // yet resolved. This task lives across page-loads to enable this bundling.
+ if (pending_require_user_mediation_) {
+ pending_require_user_mediation_->AddOrigin(page_url);
+ } else {
+ pending_require_user_mediation_.reset(
+ new password_manager::
+ CredentialManagerPendingRequireUserMediationTask(
+ this, page_url, std::vector<std::string>()));
+
+ // This will result in a callback to
+ // CredentialManagerPendingSignedOutTask::OnGetPasswordStoreResults().
+ store->GetAutofillableLogins(pending_require_user_mediation_.get());
+ }
+ }
+
+ // Acknowledge the page's signOut notification without waiting for the
+ // PasswordStore interaction to complete. The implementation of the sign-out
+ // notification isn't the page's concern.
+ ResolvePromise(request_id);
+}
+
+void CredentialManager::WebStateDestroyed() {
+ // When the WebState is destroyed, clean up everything that depends on it.
+ js_manager_.reset();
+}
+
+bool CredentialManager::IsZeroClickAllowed() const {
+ // Zero-click sign-in is only allowed when the user hasn't turned it off and
+ // when the user isn't in incognito mode.
+ return *zero_click_sign_in_enabled_ && !client_->IsOffTheRecord();
+}
+
+GURL CredentialManager::GetOrigin() const {
+ web::URLVerificationTrustLevel trust_level =
+ web::URLVerificationTrustLevel::kNone;
+ const GURL page_url(web_state()->GetCurrentURL(&trust_level));
+ DCHECK_EQ(trust_level, web::URLVerificationTrustLevel::kAbsolute);
+ return page_url;
+}
+
+void CredentialManager::SendCredential(
+ int request_id,
+ const password_manager::CredentialInfo& credential) {
+ // Invoked when the asynchronous interaction with the PasswordStore completes,
+ // this function forwards a |credential| back to the page via |js_manager_| by
+ // resolving the JavaScript Promise associated with |request_id|.
+ base::WeakPtr<CredentialManager> weak_this = weak_factory_.GetWeakPtr();
+ [js_manager_
+ resolvePromiseWithRequestID:request_id
+ credential:WebCredentialFromCredentialInfo(credential)
+ completionHandler:^(BOOL) {
+ if (weak_this)
+ weak_this->pending_request_.reset();
+ }];
+}
+
+password_manager::PasswordManagerClient* CredentialManager::client() const {
+ return client_;
+}
+
+autofill::PasswordForm CredentialManager::GetSynthesizedFormForOrigin() const {
+ autofill::PasswordForm synthetic_form;
+ synthetic_form.origin = web_state()->GetLastCommittedURL().GetOrigin();
+ synthetic_form.signon_realm = synthetic_form.origin.spec();
+ synthetic_form.scheme = autofill::PasswordForm::SCHEME_HTML;
+ synthetic_form.ssl_valid = synthetic_form.origin.SchemeIsCryptographic() &&
+ !client_->DidLastPageLoadEncounterSSLErrors();
+ return synthetic_form;
+}
+
+void CredentialManager::OnProvisionalSaveComplete() {
+ // Invoked after a credential sent up by the page was stored in a FormManager
+ // by |SignedIn|, this function asks the user if the password should be stored
+ // in the password manager.
+ DCHECK(form_manager_);
+ if (client_->IsSavingAndFillingEnabledForCurrentPage() &&
+ !form_manager_->IsBlacklisted()) {
+ client_->PromptUserToSaveOrUpdatePassword(
+ form_manager_.Pass(),
+ password_manager::CredentialSourceType::CREDENTIAL_SOURCE_API, false);
+ }
+}
+
+password_manager::PasswordStore* CredentialManager::GetPasswordStore() {
+ return client_ ? client_->GetPasswordStore() : nullptr;
+}
+
+void CredentialManager::DoneRequiringUserMediation() {
+ // Invoked when the PasswordStore has finished processing the list of origins
+ // that should have zero-click sign-in disabled.
+ DCHECK(pending_require_user_mediation_);
+ pending_require_user_mediation_.reset();
+}
+
+void CredentialManager::ResolvePromise(int request_id) {
+ [js_manager_ resolvePromiseWithRequestID:request_id completionHandler:nil];
+}
+
+void CredentialManager::RejectPromise(int request_id, ErrorType error_type) {
+ NSString* type = nil;
+ NSString* message = nil;
+ switch (error_type) {
+ case ERROR_TYPE_PENDING_REQUEST:
+ type = base::SysUTF8ToNSString(kCredentialsPendingRequestErrorType);
+ message = base::SysUTF8ToNSString(kCredentialsPendingRequestErrorMessage);
+ break;
+ case ERROR_TYPE_PASSWORD_STORE_UNAVAILABLE:
+ type = base::SysUTF8ToNSString(
+ kCredentialsPasswordStoreUnavailableErrorType);
+ message = base::SysUTF8ToNSString(
+ kCredentialsPasswordStoreUnavailableErrorMessage);
+ break;
+ case ERROR_TYPE_SECURITY_ERROR_UNTRUSTED_ORIGIN:
+ type = base::SysUTF8ToNSString(kCredentialsSecurityErrorType);
+ message = base::SysUTF8ToNSString(
+ kCredentialsSecurityErrorMessageUntrustedOrigin);
+ break;
+ }
+ [js_manager_ rejectPromiseWithRequestID:request_id
+ errorType:type
+ message:message
+ completionHandler:nil];
+}
+
+bool CredentialManager::GetUrlWithAbsoluteTrust(GURL* page_url) {
+ web::URLVerificationTrustLevel trust_level =
+ web::URLVerificationTrustLevel::kNone;
+ const GURL possibly_untrusted_url(web_state()->GetCurrentURL(&trust_level));
+ if (trust_level == web::URLVerificationTrustLevel::kAbsolute) {
+ *page_url = possibly_untrusted_url;
+ return true;
+ }
+ return false;
+}