blob: 5572d7f75d9c12351fab91bcf19aa36c0d45a5e3 [file] [log] [blame]
// Copyright 2014 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.
#include "modules/credentialmanager/CredentialsContainer.h"
#include <memory>
#include <utility>
#include "bindings/core/v8/Dictionary.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/ScriptPromise.h"
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "core/dom/DOMException.h"
#include "core/dom/Document.h"
#include "core/dom/ExceptionCode.h"
#include "core/dom/ExecutionContext.h"
#include "core/frame/Frame.h"
#include "core/frame/UseCounter.h"
#include "core/inspector/ConsoleMessage.h"
#include "core/page/FrameTree.h"
#include "modules/credentialmanager/Credential.h"
#include "modules/credentialmanager/CredentialCreationOptions.h"
#include "modules/credentialmanager/CredentialManagerClient.h"
#include "modules/credentialmanager/CredentialRequestOptions.h"
#include "modules/credentialmanager/FederatedCredential.h"
#include "modules/credentialmanager/FederatedCredentialRequestOptions.h"
#include "modules/credentialmanager/PasswordCredential.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "platform/wtf/PtrUtil.h"
#include "public/platform/Platform.h"
#include "public/platform/WebCredential.h"
#include "public/platform/WebCredentialManagerClient.h"
#include "public/platform/WebCredentialManagerError.h"
#include "public/platform/WebCredentialMediationRequirement.h"
#include "public/platform/WebFederatedCredential.h"
#include "public/platform/WebPasswordCredential.h"
namespace blink {
static void RejectDueToCredentialManagerError(
ScriptPromiseResolver* resolver,
WebCredentialManagerError reason) {
switch (reason) {
case kWebCredentialManagerDisabledError:
resolver->Reject(DOMException::Create(
kInvalidStateError, "The credential manager is disabled."));
break;
case kWebCredentialManagerPendingRequestError:
resolver->Reject(DOMException::Create(kInvalidStateError,
"A 'get()' request is pending."));
break;
case kWebCredentialManagerUnknownError:
default:
resolver->Reject(DOMException::Create(kNotReadableError,
"An unknown error occurred while "
"talking to the credential "
"manager."));
break;
}
}
class NotificationCallbacks
: public WebCredentialManagerClient::NotificationCallbacks {
WTF_MAKE_NONCOPYABLE(NotificationCallbacks);
public:
explicit NotificationCallbacks(ScriptPromiseResolver* resolver)
: resolver_(resolver) {}
~NotificationCallbacks() override {}
void OnSuccess() override {
Frame* frame =
ToDocument(ExecutionContext::From(resolver_->GetScriptState()))
->GetFrame();
SECURITY_CHECK(!frame || frame == frame->Tree().Top());
resolver_->Resolve();
}
void OnError(WebCredentialManagerError reason) override {
RejectDueToCredentialManagerError(resolver_, reason);
}
private:
const Persistent<ScriptPromiseResolver> resolver_;
};
class RequestCallbacks : public WebCredentialManagerClient::RequestCallbacks {
WTF_MAKE_NONCOPYABLE(RequestCallbacks);
public:
explicit RequestCallbacks(ScriptPromiseResolver* resolver)
: resolver_(resolver) {}
~RequestCallbacks() override {}
void OnSuccess(std::unique_ptr<WebCredential> web_credential) override {
ExecutionContext* context =
ExecutionContext::From(resolver_->GetScriptState());
if (!context)
return;
Frame* frame = ToDocument(context)->GetFrame();
SECURITY_CHECK(!frame || frame == frame->Tree().Top());
std::unique_ptr<WebCredential> credential =
WTF::WrapUnique(web_credential.release());
if (!credential || !frame) {
resolver_->Resolve();
return;
}
DCHECK(credential->IsPasswordCredential() ||
credential->IsFederatedCredential());
UseCounter::Count(ExecutionContext::From(resolver_->GetScriptState()),
UseCounter::kCredentialManagerGetReturnedCredential);
if (credential->IsPasswordCredential())
resolver_->Resolve(PasswordCredential::Create(
static_cast<WebPasswordCredential*>(credential.get())));
else
resolver_->Resolve(FederatedCredential::Create(
static_cast<WebFederatedCredential*>(credential.get())));
}
void OnError(WebCredentialManagerError reason) override {
RejectDueToCredentialManagerError(resolver_, reason);
}
private:
const Persistent<ScriptPromiseResolver> resolver_;
};
CredentialsContainer* CredentialsContainer::Create() {
return new CredentialsContainer();
}
CredentialsContainer::CredentialsContainer() {}
static bool CheckBoilerplate(ScriptPromiseResolver* resolver) {
Frame* frame = ToDocument(ExecutionContext::From(resolver->GetScriptState()))
->GetFrame();
if (!frame || frame != frame->Tree().Top()) {
resolver->Reject(DOMException::Create(kSecurityError,
"CredentialContainer methods may "
"only be executed in a top-level "
"document."));
return false;
}
String error_message;
if (!ExecutionContext::From(resolver->GetScriptState())
->IsSecureContext(error_message)) {
resolver->Reject(DOMException::Create(kSecurityError, error_message));
return false;
}
CredentialManagerClient* client = CredentialManagerClient::From(
ExecutionContext::From(resolver->GetScriptState()));
if (!client) {
resolver->Reject(DOMException::Create(
kInvalidStateError,
"Could not establish connection to the credential manager."));
return false;
}
return true;
}
ScriptPromise CredentialsContainer::get(
ScriptState* script_state,
const CredentialRequestOptions& options) {
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
if (!CheckBoilerplate(resolver))
return promise;
ExecutionContext* context = ExecutionContext::From(script_state);
// Set the default mediation option if none is provided.
// If both 'unmediated' and 'mediation' are set log a warning if they are
// contradicting.
// Also sets 'mediation' appropriately when only 'unmediated' is set.
// TODO(http://crbug.com/715077): Remove this when 'unmediated' is removed.
String mediation = "optional";
if (options.hasUnmediated() && !options.hasMediation()) {
mediation = options.unmediated() ? "silent" : "optional";
UseCounter::Count(
context,
UseCounter::kCredentialManagerCredentialRequestOptionsOnlyUnmediated);
} else if (options.hasMediation()) {
mediation = options.mediation();
if (options.hasUnmediated() &&
((options.unmediated() && options.mediation() != "silent") ||
(!options.unmediated() && options.mediation() != "optional"))) {
context->AddConsoleMessage(ConsoleMessage::Create(
kJSMessageSource, kWarningMessageLevel,
"mediation: '" + options.mediation() + "' overrides unmediated: " +
(options.unmediated() ? "true" : "false") + "."));
}
}
Vector<KURL> providers;
if (options.hasFederated() && options.federated().hasProviders()) {
for (const auto& string : options.federated().providers()) {
KURL url = KURL(KURL(), string);
if (url.IsValid())
providers.push_back(std::move(url));
}
}
WebCredentialMediationRequirement requirement;
if (mediation == "silent") {
UseCounter::Count(context,
UseCounter::kCredentialManagerGetMediationSilent);
requirement = WebCredentialMediationRequirement::kSilent;
} else if (mediation == "optional") {
UseCounter::Count(context,
UseCounter::kCredentialManagerGetMediationOptional);
requirement = WebCredentialMediationRequirement::kOptional;
} else {
DCHECK_EQ("required", mediation);
UseCounter::Count(context,
UseCounter::kCredentialManagerGetMediationRequired);
requirement = WebCredentialMediationRequirement::kRequired;
}
CredentialManagerClient::From(context)->DispatchGet(
requirement, options.password(), providers,
new RequestCallbacks(resolver));
return promise;
}
ScriptPromise CredentialsContainer::store(ScriptState* script_state,
Credential* credential) {
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
if (!CheckBoilerplate(resolver))
return promise;
auto web_credential =
WebCredential::Create(credential->GetPlatformCredential());
CredentialManagerClient::From(ExecutionContext::From(script_state))
->DispatchStore(*web_credential, new NotificationCallbacks(resolver));
return promise;
}
ScriptPromise CredentialsContainer::create(
ScriptState* script_state,
const CredentialCreationOptions& options,
ExceptionState& exception_state) {
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
if (!CheckBoilerplate(resolver))
return promise;
// TODO(http://crbug.com/715077): Generalize this check when 'publicKey'
// becomes a supported option.
if (!(options.hasPassword() ^ options.hasFederated())) {
resolver->Reject(DOMException::Create(kNotSupportedError,
"Only 'password' and 'federated' "
"credential types are currently "
"supported."));
return promise;
}
if (options.hasPassword()) {
if (options.password().isPasswordCredentialData()) {
resolver->Resolve(PasswordCredential::Create(
options.password().getAsPasswordCredentialData(), exception_state));
} else {
resolver->Resolve(PasswordCredential::Create(
options.password().getAsHTMLFormElement(), exception_state));
}
} else {
resolver->Resolve(
FederatedCredential::Create(options.federated(), exception_state));
}
return promise;
}
ScriptPromise CredentialsContainer::preventSilentAccess(
ScriptState* script_state) {
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
if (!CheckBoilerplate(resolver))
return promise;
CredentialManagerClient::From(ExecutionContext::From(script_state))
->DispatchPreventSilentAccess(new NotificationCallbacks(resolver));
return promise;
}
ScriptPromise CredentialsContainer::requireUserMediation(
ScriptState* script_state) {
return preventSilentAccess(script_state);
}
} // namespace blink