OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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 |
| 5 #import "ios/chrome/browser/passwords/credential_manager.h" |
| 6 |
| 7 #include "base/ios/ios_util.h" |
| 8 #import "base/ios/weak_nsobject.h" |
| 9 #include "base/mac/bind_objc_block.h" |
| 10 #include "base/memory/scoped_vector.h" |
| 11 #include "base/message_loop/message_loop.h" |
| 12 #include "base/strings/sys_string_conversions.h" |
| 13 #include "components/password_manager/core/browser/password_store_consumer.h" |
| 14 #include "components/password_manager/core/common/credential_manager_types.h" |
| 15 #include "components/password_manager/core/common/password_manager_pref_names.h" |
| 16 #import "ios/chrome/browser/passwords/js_credential_manager.h" |
| 17 #import "ios/web/public/url_scheme_util.h" |
| 18 #include "ios/web/public/web_state/credential.h" |
| 19 #include "ios/web/public/web_state/url_verification_constants.h" |
| 20 #include "ios/web/public/web_state/web_state.h" |
| 21 |
| 22 namespace { |
| 23 |
| 24 // Converts a password_manager::CredentialInfo to a web::Credential. |
| 25 web::Credential WebCredentialFromCredentialInfo( |
| 26 const password_manager::CredentialInfo& credential_info) { |
| 27 web::Credential credential; |
| 28 switch (credential_info.type) { |
| 29 case password_manager::CredentialType::CREDENTIAL_TYPE_EMPTY: |
| 30 credential.type = web::CredentialType::CREDENTIAL_TYPE_EMPTY; |
| 31 break; |
| 32 case password_manager::CredentialType::CREDENTIAL_TYPE_PASSWORD: |
| 33 credential.type = web::CredentialType::CREDENTIAL_TYPE_PASSWORD; |
| 34 break; |
| 35 case password_manager::CredentialType::CREDENTIAL_TYPE_FEDERATED: |
| 36 credential.type = web::CredentialType::CREDENTIAL_TYPE_FEDERATED; |
| 37 break; |
| 38 } |
| 39 credential.id = credential_info.id; |
| 40 credential.name = credential_info.name; |
| 41 credential.avatar_url = credential_info.icon; |
| 42 credential.password = credential_info.password; |
| 43 credential.federation_url = credential_info.federation; |
| 44 return credential; |
| 45 } |
| 46 |
| 47 // Converts a web::Credential to a password_manager::CredentialInfo. |
| 48 password_manager::CredentialInfo CredentialInfoFromWebCredential( |
| 49 const web::Credential& credential) { |
| 50 password_manager::CredentialInfo credential_info; |
| 51 switch (credential.type) { |
| 52 case web::CredentialType::CREDENTIAL_TYPE_EMPTY: |
| 53 credential_info.type = |
| 54 password_manager::CredentialType::CREDENTIAL_TYPE_EMPTY; |
| 55 break; |
| 56 case web::CredentialType::CREDENTIAL_TYPE_PASSWORD: |
| 57 credential_info.type = |
| 58 password_manager::CredentialType::CREDENTIAL_TYPE_PASSWORD; |
| 59 break; |
| 60 case web::CredentialType::CREDENTIAL_TYPE_FEDERATED: |
| 61 credential_info.type = |
| 62 password_manager::CredentialType::CREDENTIAL_TYPE_FEDERATED; |
| 63 break; |
| 64 } |
| 65 credential_info.id = credential.id; |
| 66 credential_info.name = credential.name; |
| 67 credential_info.icon = credential.avatar_url; |
| 68 credential_info.password = credential.password; |
| 69 credential_info.federation = credential.federation_url; |
| 70 return credential_info; |
| 71 } |
| 72 |
| 73 } // namespace |
| 74 |
| 75 CredentialManager::CredentialManager( |
| 76 web::WebState* web_state, |
| 77 password_manager::PasswordManagerClient* client, |
| 78 password_manager::PasswordManagerDriver* driver, |
| 79 JSCredentialManager* js_manager) |
| 80 : web::WebStateObserver(web_state), |
| 81 pending_request_(nullptr), |
| 82 form_manager_(nullptr), |
| 83 js_manager_([js_manager retain]), |
| 84 client_(client), |
| 85 driver_(driver), |
| 86 weak_factory_(this) { |
| 87 zero_click_sign_in_enabled_.Init( |
| 88 password_manager::prefs::kPasswordManagerAutoSignin, client_->GetPrefs()); |
| 89 } |
| 90 |
| 91 CredentialManager::~CredentialManager() = default; |
| 92 |
| 93 void CredentialManager::PageLoaded( |
| 94 web::PageLoadCompletionStatus load_completion_status) { |
| 95 // Ensure the JavaScript is loaded when the page finishes loading. |
| 96 web::URLVerificationTrustLevel trust_level = |
| 97 web::URLVerificationTrustLevel::kNone; |
| 98 const GURL page_url(web_state()->GetCurrentURL(&trust_level)); |
| 99 if (!base::ios::IsRunningOnIOS8OrLater() || |
| 100 trust_level != web::URLVerificationTrustLevel::kAbsolute || |
| 101 !web::UrlHasWebScheme(page_url) || !web_state()->ContentIsHTML()) { |
| 102 return; |
| 103 } |
| 104 [js_manager_ inject]; |
| 105 } |
| 106 |
| 107 void CredentialManager::CredentialsRequested( |
| 108 int request_id, |
| 109 const GURL& source_url, |
| 110 bool zero_click_only, |
| 111 const std::vector<std::string>& federations, |
| 112 bool is_user_initiated) { |
| 113 // Invoked when the page invokes navigator.credentials.request(), this |
| 114 // function will attempt to retrieve a Credential from the PasswordStore that |
| 115 // meets the specified parameters and, if successful, send it back to the page |
| 116 // via SendCredential. |
| 117 DCHECK_GE(request_id, 0); |
| 118 password_manager::PasswordStore* store = GetPasswordStore(); |
| 119 |
| 120 // If there's an outstanding request, or the PasswordStore isn't loaded yet, |
| 121 // the request should fail outright and the JS Promise should be rejected |
| 122 // with an appropriate error. |
| 123 if (pending_request_ || !store) { |
| 124 base::MessageLoop::current()->PostTask( |
| 125 FROM_HERE, |
| 126 base::Bind(&CredentialManager::RejectPromise, |
| 127 weak_factory_.GetWeakPtr(), request_id, |
| 128 pending_request_ ? ERROR_TYPE_PENDING_REQUEST |
| 129 : ERROR_TYPE_PASSWORD_STORE_UNAVAILABLE)); |
| 130 return; |
| 131 } |
| 132 |
| 133 // If the page requested a zero-click credential -- one that can be returned |
| 134 // without first asking the user -- and if zero-click isn't currently |
| 135 // available, send back an empty credential. |
| 136 if (zero_click_only && !IsZeroClickAllowed()) { |
| 137 base::MessageLoop::current()->PostTask( |
| 138 FROM_HERE, base::Bind(&CredentialManager::SendCredential, |
| 139 weak_factory_.GetWeakPtr(), request_id, |
| 140 password_manager::CredentialInfo())); |
| 141 return; |
| 142 } |
| 143 |
| 144 // If the page origin is untrusted, the request should be rejected. |
| 145 GURL page_url; |
| 146 if (!GetUrlWithAbsoluteTrust(&page_url)) { |
| 147 RejectPromise(request_id, ERROR_TYPE_SECURITY_ERROR_UNTRUSTED_ORIGIN); |
| 148 return; |
| 149 } |
| 150 |
| 151 // Bundle up the arguments and forward them to the PasswordStore, which will |
| 152 // asynchronously return the resulting Credential by invoking |
| 153 // |SendCredential|. |
| 154 std::vector<GURL> federation_urls; |
| 155 for (const auto& federation : federations) |
| 156 federation_urls.push_back(GURL(federation)); |
| 157 std::vector<std::string> realms; |
| 158 pending_request_.reset( |
| 159 new password_manager::CredentialManagerPendingRequestTask( |
| 160 this, request_id, zero_click_only, page_url, federation_urls, |
| 161 realms)); |
| 162 store->GetAutofillableLogins(pending_request_.get()); |
| 163 } |
| 164 |
| 165 void CredentialManager::SignedIn(int request_id, |
| 166 const GURL& source_url, |
| 167 const web::Credential& credential) { |
| 168 // Invoked when the page invokes navigator.credentials.notifySignedIn(), this |
| 169 // function stores the signed-in |credential| and sends a message back to the |
| 170 // page to resolve the Promise associated with |request_id|. |
| 171 DCHECK(credential.type != web::CredentialType::CREDENTIAL_TYPE_EMPTY); |
| 172 DCHECK_GE(request_id, 0); |
| 173 |
| 174 // Requests from untrusted origins should be rejected. |
| 175 GURL page_url; |
| 176 if (!GetUrlWithAbsoluteTrust(&page_url)) { |
| 177 RejectPromise(request_id, ERROR_TYPE_SECURITY_ERROR_UNTRUSTED_ORIGIN); |
| 178 return; |
| 179 } |
| 180 |
| 181 // Notify the page that the notification was successful to avoid blocking the |
| 182 // page while storing the credential. This is okay because the notification |
| 183 // doesn't imply that the credential will be stored, just that it might be. |
| 184 // It isn't the page's concern to know whether the storage took place or not. |
| 185 ResolvePromise(request_id); |
| 186 |
| 187 // Do nothing if the password manager isn't active. |
| 188 if (!client_->IsSavingAndFillingEnabledForCurrentPage()) |
| 189 return; |
| 190 |
| 191 // Store the signed-in credential so that the user can save it, if desired. |
| 192 // Prompting the user and saving are handled by the PasswordFormManager. |
| 193 scoped_ptr<autofill::PasswordForm> form( |
| 194 password_manager::CreatePasswordFormFromCredentialInfo( |
| 195 CredentialInfoFromWebCredential(credential), page_url)); |
| 196 form->skip_zero_click = !IsZeroClickAllowed(); |
| 197 |
| 198 // TODO(mkwst): This is a stub; we should be checking the PasswordStore to |
| 199 // determine whether or not the credential exists, and calling UpdateLogin |
| 200 // accordingly. |
| 201 form_manager_.reset( |
| 202 new password_manager::CredentialManagerPasswordFormManager( |
| 203 client_, driver_->AsWeakPtr(), *form, this)); |
| 204 } |
| 205 |
| 206 void CredentialManager::SignedOut(int request_id, const GURL& source_url) { |
| 207 // Invoked when the page invokes navigator.credentials.notifySignedOut, this |
| 208 // function notifies the PasswordStore that zero-click sign-in should be |
| 209 // disabled for the current page origin. |
| 210 DCHECK_GE(request_id, 0); |
| 211 |
| 212 // Requests from untrusted origins should be rejected. |
| 213 GURL page_url; |
| 214 if (!GetUrlWithAbsoluteTrust(&page_url)) { |
| 215 RejectPromise(request_id, ERROR_TYPE_SECURITY_ERROR_UNTRUSTED_ORIGIN); |
| 216 return; |
| 217 } |
| 218 |
| 219 // The user signed out of the current page, so future zero-click credential |
| 220 // requests for this page should fail: otherwise, the next time the user |
| 221 // visits the page, if zero-click requests succeeded, the user might be auto- |
| 222 // signed-in again with the credential that they just signed out. Forward this |
| 223 // information to the PasswordStore via an asynchronous task. |
| 224 password_manager::PasswordStore* store = GetPasswordStore(); |
| 225 if (store) { |
| 226 // Bundle the origins that are sent to the PasswordStore if the task hasn't |
| 227 // yet resolved. This task lives across page-loads to enable this bundling. |
| 228 if (pending_require_user_mediation_) { |
| 229 pending_require_user_mediation_->AddOrigin(page_url); |
| 230 } else { |
| 231 pending_require_user_mediation_.reset( |
| 232 new password_manager:: |
| 233 CredentialManagerPendingRequireUserMediationTask( |
| 234 this, page_url, std::vector<std::string>())); |
| 235 |
| 236 // This will result in a callback to |
| 237 // CredentialManagerPendingSignedOutTask::OnGetPasswordStoreResults(). |
| 238 store->GetAutofillableLogins(pending_require_user_mediation_.get()); |
| 239 } |
| 240 } |
| 241 |
| 242 // Acknowledge the page's signOut notification without waiting for the |
| 243 // PasswordStore interaction to complete. The implementation of the sign-out |
| 244 // notification isn't the page's concern. |
| 245 ResolvePromise(request_id); |
| 246 } |
| 247 |
| 248 void CredentialManager::WebStateDestroyed() { |
| 249 // When the WebState is destroyed, clean up everything that depends on it. |
| 250 js_manager_.reset(); |
| 251 } |
| 252 |
| 253 bool CredentialManager::IsZeroClickAllowed() const { |
| 254 // Zero-click sign-in is only allowed when the user hasn't turned it off and |
| 255 // when the user isn't in incognito mode. |
| 256 return *zero_click_sign_in_enabled_ && !client_->IsOffTheRecord(); |
| 257 } |
| 258 |
| 259 GURL CredentialManager::GetOrigin() const { |
| 260 web::URLVerificationTrustLevel trust_level = |
| 261 web::URLVerificationTrustLevel::kNone; |
| 262 const GURL page_url(web_state()->GetCurrentURL(&trust_level)); |
| 263 DCHECK_EQ(trust_level, web::URLVerificationTrustLevel::kAbsolute); |
| 264 return page_url; |
| 265 } |
| 266 |
| 267 void CredentialManager::SendCredential( |
| 268 int request_id, |
| 269 const password_manager::CredentialInfo& credential) { |
| 270 // Invoked when the asynchronous interaction with the PasswordStore completes, |
| 271 // this function forwards a |credential| back to the page via |js_manager_| by |
| 272 // resolving the JavaScript Promise associated with |request_id|. |
| 273 base::WeakPtr<CredentialManager> weak_this = weak_factory_.GetWeakPtr(); |
| 274 [js_manager_ |
| 275 resolvePromiseWithRequestID:request_id |
| 276 credential:WebCredentialFromCredentialInfo(credential) |
| 277 completionHandler:^(BOOL) { |
| 278 if (weak_this) |
| 279 weak_this->pending_request_.reset(); |
| 280 }]; |
| 281 } |
| 282 |
| 283 password_manager::PasswordManagerClient* CredentialManager::client() const { |
| 284 return client_; |
| 285 } |
| 286 |
| 287 autofill::PasswordForm CredentialManager::GetSynthesizedFormForOrigin() const { |
| 288 autofill::PasswordForm synthetic_form; |
| 289 synthetic_form.origin = web_state()->GetLastCommittedURL().GetOrigin(); |
| 290 synthetic_form.signon_realm = synthetic_form.origin.spec(); |
| 291 synthetic_form.scheme = autofill::PasswordForm::SCHEME_HTML; |
| 292 synthetic_form.ssl_valid = synthetic_form.origin.SchemeIsCryptographic() && |
| 293 !client_->DidLastPageLoadEncounterSSLErrors(); |
| 294 return synthetic_form; |
| 295 } |
| 296 |
| 297 void CredentialManager::OnProvisionalSaveComplete() { |
| 298 // Invoked after a credential sent up by the page was stored in a FormManager |
| 299 // by |SignedIn|, this function asks the user if the password should be stored |
| 300 // in the password manager. |
| 301 DCHECK(form_manager_); |
| 302 if (client_->IsSavingAndFillingEnabledForCurrentPage() && |
| 303 !form_manager_->IsBlacklisted()) { |
| 304 client_->PromptUserToSaveOrUpdatePassword( |
| 305 form_manager_.Pass(), |
| 306 password_manager::CredentialSourceType::CREDENTIAL_SOURCE_API, false); |
| 307 } |
| 308 } |
| 309 |
| 310 password_manager::PasswordStore* CredentialManager::GetPasswordStore() { |
| 311 return client_ ? client_->GetPasswordStore() : nullptr; |
| 312 } |
| 313 |
| 314 void CredentialManager::DoneRequiringUserMediation() { |
| 315 // Invoked when the PasswordStore has finished processing the list of origins |
| 316 // that should have zero-click sign-in disabled. |
| 317 DCHECK(pending_require_user_mediation_); |
| 318 pending_require_user_mediation_.reset(); |
| 319 } |
| 320 |
| 321 void CredentialManager::ResolvePromise(int request_id) { |
| 322 [js_manager_ resolvePromiseWithRequestID:request_id completionHandler:nil]; |
| 323 } |
| 324 |
| 325 void CredentialManager::RejectPromise(int request_id, ErrorType error_type) { |
| 326 NSString* type = nil; |
| 327 NSString* message = nil; |
| 328 switch (error_type) { |
| 329 case ERROR_TYPE_PENDING_REQUEST: |
| 330 type = base::SysUTF8ToNSString(kCredentialsPendingRequestErrorType); |
| 331 message = base::SysUTF8ToNSString(kCredentialsPendingRequestErrorMessage); |
| 332 break; |
| 333 case ERROR_TYPE_PASSWORD_STORE_UNAVAILABLE: |
| 334 type = base::SysUTF8ToNSString( |
| 335 kCredentialsPasswordStoreUnavailableErrorType); |
| 336 message = base::SysUTF8ToNSString( |
| 337 kCredentialsPasswordStoreUnavailableErrorMessage); |
| 338 break; |
| 339 case ERROR_TYPE_SECURITY_ERROR_UNTRUSTED_ORIGIN: |
| 340 type = base::SysUTF8ToNSString(kCredentialsSecurityErrorType); |
| 341 message = base::SysUTF8ToNSString( |
| 342 kCredentialsSecurityErrorMessageUntrustedOrigin); |
| 343 break; |
| 344 } |
| 345 [js_manager_ rejectPromiseWithRequestID:request_id |
| 346 errorType:type |
| 347 message:message |
| 348 completionHandler:nil]; |
| 349 } |
| 350 |
| 351 bool CredentialManager::GetUrlWithAbsoluteTrust(GURL* page_url) { |
| 352 web::URLVerificationTrustLevel trust_level = |
| 353 web::URLVerificationTrustLevel::kNone; |
| 354 const GURL possibly_untrusted_url(web_state()->GetCurrentURL(&trust_level)); |
| 355 if (trust_level == web::URLVerificationTrustLevel::kAbsolute) { |
| 356 *page_url = possibly_untrusted_url; |
| 357 return true; |
| 358 } |
| 359 return false; |
| 360 } |
OLD | NEW |