blob: e865fbf7fa1c0bedf8ed9eeb1a67bafb94f83214 [file] [log] [blame]
// Copyright 2017 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/strings/utf_string_conversions.h"
#include "ios/chrome/browser/passwords/credential_manager_util.h"
#include "ios/chrome/browser/passwords/js_credential_manager.h"
#import "ios/web/public/web_state/web_state.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using password_manager::CredentialManagerError;
using password_manager::CredentialInfo;
using password_manager::CredentialType;
using password_manager::CredentialMediationRequirement;
namespace {
// Script command prefix for CM API calls. Possible commands to be sent from
// injected JS are 'credentials.get', 'credentials.store' and
// 'credentials.preventSilentAccess'.
constexpr char kCommandPrefix[] = "credentials";
} // namespace
CredentialManager::CredentialManager(
password_manager::PasswordManagerClient* client,
web::WebState* web_state)
: impl_(client), web_state_(web_state) {
web_state_->AddScriptCommandCallback(
base::Bind(&CredentialManager::HandleScriptCommand,
base::Unretained(this)),
kCommandPrefix);
}
CredentialManager::~CredentialManager() {
web_state_->RemoveScriptCommandCallback(kCommandPrefix);
}
bool CredentialManager::HandleScriptCommand(const base::DictionaryValue& json,
const GURL& origin_url,
bool user_is_interacting,
bool is_main_frame,
web::WebFrame* sender_frame) {
if (!is_main_frame) {
// Credentials manager is only supported on main frame.
return false;
}
double promise_id_double = -1;
// |promiseId| field should be an integer value, but since JavaScript has only
// one type for numbers (64-bit float), all numbers in the messages are sent
// as doubles.
if (!json.GetDouble("promiseId", &promise_id_double)) {
DLOG(ERROR) << "Received bad json - no valid 'promiseId' field";
return false;
}
int promise_id = static_cast<int>(promise_id_double);
if (!WebStateContentIsSecureHtml(web_state_)) {
RejectCredentialPromiseWithInvalidStateError(
web_state_, promise_id,
base::ASCIIToUTF16(
"Credential Manager API called from insecure context"));
return true;
}
std::string command;
if (!json.GetString("command", &command)) {
DLOG(ERROR) << "Received bad json - no valid 'command' field";
return false;
}
if (command == "credentials.get") {
CredentialMediationRequirement mediation;
if (!ParseMediationRequirement(json, &mediation)) {
RejectCredentialPromiseWithTypeError(
web_state_, promise_id,
base::ASCIIToUTF16(
"CredentialRequestOptions: Invalid 'mediation' value."));
return true;
}
bool include_passwords;
if (!ParseIncludePasswords(json, &include_passwords)) {
RejectCredentialPromiseWithTypeError(
web_state_, promise_id,
base::ASCIIToUTF16(
"CredentialRequestOptions: Invalid 'password' value."));
return true;
}
std::vector<GURL> federations;
if (!ParseFederations(json, &federations)) {
RejectCredentialPromiseWithTypeError(
web_state_, promise_id,
base::ASCIIToUTF16(
"CredentialRequestOptions: invalid 'providers' value."));
return true;
}
impl_.Get(mediation, include_passwords, federations,
base::BindOnce(&CredentialManager::SendGetResponse,
base::Unretained(this), promise_id));
return true;
}
if (command == "credentials.store") {
CredentialInfo credential;
std::string parse_message;
if (!ParseCredentialDictionary(json, &credential, &parse_message)) {
RejectCredentialPromiseWithTypeError(web_state_, promise_id,
base::UTF8ToUTF16(parse_message));
return false;
}
impl_.Store(credential,
base::BindOnce(&CredentialManager::SendStoreResponse,
base::Unretained(this), promise_id));
return true;
}
if (command == "credentials.preventSilentAccess") {
impl_.PreventSilentAccess(
base::BindOnce(&CredentialManager::SendPreventSilentAccessResponse,
base::Unretained(this), promise_id));
return true;
}
return false;
}
void CredentialManager::SendGetResponse(
int promise_id,
CredentialManagerError error,
const base::Optional<CredentialInfo>& info) {
switch (error) {
case CredentialManagerError::SUCCESS:
ResolveCredentialPromiseWithCredentialInfo(web_state_, promise_id, info);
break;
case CredentialManagerError::PENDING_REQUEST:
RejectCredentialPromiseWithInvalidStateError(
web_state_, promise_id,
base::ASCIIToUTF16("Pending 'get()' request."));
break;
case CredentialManagerError::PASSWORDSTOREUNAVAILABLE:
RejectCredentialPromiseWithNotSupportedError(
web_state_, promise_id,
base::ASCIIToUTF16("Password store is unavailable."));
break;
case CredentialManagerError::UNKNOWN:
RejectCredentialPromiseWithNotSupportedError(
web_state_, promise_id,
base::ASCIIToUTF16("Unknown error has occurred."));
}
}
void CredentialManager::SendPreventSilentAccessResponse(int promise_id) {
ResolveCredentialPromiseWithUndefined(web_state_, promise_id);
}
void CredentialManager::SendStoreResponse(int promise_id) {
ResolveCredentialPromiseWithUndefined(web_state_, promise_id);
}