Commit 0112af76 authored by Kim Paulhamus's avatar Kim Paulhamus Committed by Commit Bot

Initial plumbing of isUserVerifyingPlatformAuthenticatorAvailable().

This method lets the relying party determine if the browser is on a
device that supports platform authenticators (such as fingerprint).

We are plumbing the call through  mojom so that we can have separate
Android and desktop implementations.

Bug: 828216
Change-Id: Iaf0b7e84606b816598b1c54e9380a5acd9a4e52b
Reviewed-on: https://chromium-review.googlesource.com/1053110
Commit-Queue: Kim Paulhamus <kpaulhamus@chromium.org>
Reviewed-by: default avatarMaria Khomenko <mariakhomenko@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarBalazs Engedy <engedy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#563085}
parent e58b1a8b
......@@ -67,6 +67,12 @@ public class AuthenticatorImpl implements Authenticator, HandlerResponseCallback
onError(AuthenticatorStatus.NOT_IMPLEMENTED);
}
@Override
public void isUserVerifyingPlatformAuthenticatorAvailable(
IsUserVerifyingPlatformAuthenticatorAvailableResponse callback) {
callback.call(false);
}
/**
* Callbacks for receiving responses from the internal handlers.
*/
......
......@@ -104,7 +104,7 @@ public class AuthenticatorTest {
/**
* Verify that the Mojo bridge between Blink and Java is working for
* navigator.credentials.create. This test currently expects a
* navigator.credentials.get. This test currently expects a
* "Not Implemented" response. Testing any real response would require
* setting up or mocking a real APK.
*/
......@@ -116,4 +116,19 @@ public class AuthenticatorTest {
mActivityTestRule.runJavaScriptCodeInCurrentTab("doGetPublicKeyCredential()");
Assert.assertEquals("Success", mUpdateWaiter.waitForUpdate());
}
/**
* Verify that the Mojo bridge between Blink and Java is working for
* PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable.
* This test currently expects a "false" response.
*/
@Test
@MediumTest
@Feature({"WebAuth"})
public void testIsUserVerifyingPlatformAuthenticatorAvailable() throws Exception {
mActivityTestRule.loadUrl(mUrl);
mActivityTestRule.runJavaScriptCodeInCurrentTab(
"doIsUserVerifyingPlatformAuthenticatorAvailable()");
Assert.assertEquals("Success", mUpdateWaiter.waitForUpdate());
}
}
......@@ -674,6 +674,11 @@ void AuthenticatorImpl::GetAssertion(
}
}
void AuthenticatorImpl::IsUserVerifyingPlatformAuthenticatorAvailable(
IsUserVerifyingPlatformAuthenticatorAvailableCallback callback) {
std::move(callback).Run(false);
}
void AuthenticatorImpl::DidFinishNavigation(
NavigationHandle* navigation_handle) {
if (!navigation_handle->HasCommitted() ||
......
......@@ -103,6 +103,8 @@ class CONTENT_EXPORT AuthenticatorImpl : public webauth::mojom::Authenticator,
void GetAssertion(
webauth::mojom::PublicKeyCredentialRequestOptionsPtr options,
GetAssertionCallback callback) override;
void IsUserVerifyingPlatformAuthenticatorAvailable(
IsUserVerifyingPlatformAuthenticatorAvailableCallback callback) override;
// WebContentsObserver:
void DidFinishNavigation(NavigationHandle* navigation_handle) override;
......
......@@ -73,6 +73,30 @@
}
});
}
function doIsUserVerifyingPlatformAuthenticatorAvailable() {
if (window.PublicKeyCredential === undefined) {
window.document.title = 'Fail: isUserVerifyingPlatformAuthenticatorAvailable() === undefined';
return;
}
if (window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable === undefined) {
window.document.title = 'Fail: isUserVerifyingPlatformAuthenticatorAvailable() === undefined';
return;
}
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
.then(r => {
if (r == false) {
window.document.title = 'Success';
} else {
window.document.title = 'Fail';
}
})
.catch(e => {
window.document.title = 'Fail: ' + e.name + " " + e.message;
});
}
</script>
</head>
<body>
......
......@@ -11,6 +11,7 @@ async_test(t => {
eventWatcher.wait_for("message")
.then(_ => {
navigatorCredentials = openedWindow.navigator.credentials;
publicKeyCredential = openedWindow.PublicKeyCredential;
window.setTimeout(_ => openedWindow.location.reload());
return eventWatcher.wait_for("message");
})
......@@ -25,6 +26,8 @@ async_test(t => {
"navigator.credentials.store() should not crash nor return a Promise.");
assert_equals(navigatorCredentials.preventSilentAccess(), undefined,
"navigator.credentials.preventSilentAccess() should not crash nor return a Promise.");
assert_equals(publicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(), undefined,
"window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() should not crash nor return a Promise.");
}));
});
......
......@@ -4,9 +4,8 @@
<script src="../resources/testharnessreport.js"></script>
<script>
promise_test(t => {
return promise_rejects(t, "NotSupportedError",
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable());
}, "isUserVerifyingPlatformAuthenticatorAvailable() is not implemented.");
promise_test(async _ => {
assert_equals(await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(), false);
}, "isUserVerifyingPlatformAuthenticatorAvailable() is false by default.");
</script>
......@@ -125,6 +125,10 @@ class MockAuthenticator {
return {status, credential: response};
}
async isUserVerifyingPlatformAuthenticatorAvailable() {
return false;
}
// Resets state of mock Authenticator.
reset() {
this.status_ = webauth.mojom.AuthenticatorStatus.UNKNOWN_ERROR;
......
......@@ -279,4 +279,9 @@ interface Authenticator {
// |GetAssertionResponse| will be set if and only if status == SUCCESS.
GetAssertion(PublicKeyCredentialRequestOptions options)
=> (AuthenticatorStatus status, GetAssertionAuthenticatorResponse? credential);
// Returns true if the user platform provides an authenticator. Relying
// Parties use this method to determine whether they can create a new
// credential using a user-verifying platform authenticator.
IsUserVerifyingPlatformAuthenticatorAvailable() => (bool available);
};
......@@ -28,5 +28,7 @@ blink_modules_sources("credentialmanager") {
"password_credential.h",
"public_key_credential.cc",
"public_key_credential.h",
"scoped_promise_resolver.cc",
"scoped_promise_resolver.h",
]
}
......@@ -33,6 +33,7 @@
#include "third_party/blink/renderer/modules/credentialmanager/public_key_credential.h"
#include "third_party/blink/renderer/modules/credentialmanager/public_key_credential_creation_options.h"
#include "third_party/blink/renderer/modules/credentialmanager/public_key_credential_request_options.h"
#include "third_party/blink/renderer/modules/credentialmanager/scoped_promise_resolver.h"
#include "third_party/blink/renderer/platform/weborigin/origin_access_entry.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
......@@ -55,41 +56,6 @@ using ::webauth::mojom::blink::GetAssertionAuthenticatorResponsePtr;
enum class RequiredOriginType { kSecure, kSecureAndSameWithAncestors };
// Off-heap wrapper that holds a strong reference to a ScriptPromiseResolver.
class ScopedPromiseResolver {
WTF_MAKE_NONCOPYABLE(ScopedPromiseResolver);
public:
explicit ScopedPromiseResolver(ScriptPromiseResolver* resolver)
: resolver_(resolver) {}
~ScopedPromiseResolver() {
if (resolver_)
OnConnectionError();
}
// Releases the owned |resolver_|. This is to be called by the Mojo response
// callback responsible for resolving the corresponding ScriptPromise
//
// If this method is not called before |this| goes of scope, it is assumed
// that a Mojo connection error has occurred, and the response callback was
// never invoked. The Promise will be rejected with an appropriate exception.
ScriptPromiseResolver* Release() { return resolver_.Release(); }
private:
void OnConnectionError() {
// The only anticapted reason for a connection error is that the embedder
// does not implement mojom::CredentialManager, or mojom::AuthenticatorImpl,
// so go out on a limb and try to provide an actionable error message.
resolver_->Reject(DOMException::Create(
kNotSupportedError,
"The user agent either does not implement a password store or does "
"not support public key credentials."));
}
Persistent<ScriptPromiseResolver> resolver_;
};
bool IsSameOriginWithAncestors(const Frame* frame) {
DCHECK(frame);
const Frame* current = frame;
......
......@@ -8,6 +8,8 @@
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/dom/exception_code.h"
#include "third_party/blink/renderer/modules/credentialmanager/credential_manager_proxy.h"
#include "third_party/blink/renderer/modules/credentialmanager/scoped_promise_resolver.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
namespace blink {
......@@ -15,6 +17,12 @@ namespace blink {
namespace {
// https://www.w3.org/TR/webauthn/#dom-publickeycredential-type-slot:
constexpr char kPublicKeyCredentialType[] = "public-key";
void OnIsUserVerifyingComplete(
std::unique_ptr<ScopedPromiseResolver> scoped_resolver,
bool available) {
scoped_resolver->Release()->Resolve(available);
}
} // namespace
PublicKeyCredential* PublicKeyCredential::Create(
......@@ -38,9 +46,23 @@ PublicKeyCredential::PublicKeyCredential(
ScriptPromise
PublicKeyCredential::isUserVerifyingPlatformAuthenticatorAvailable(
ScriptState* script_state) {
return ScriptPromise::RejectWithDOMException(
script_state,
DOMException::Create(kNotSupportedError, "Operation not implemented."));
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
// Ignore calls if the current realm execution context is no longer valid,
// e.g., because the responsible document was detached.
DCHECK(resolver->GetExecutionContext());
if (resolver->GetExecutionContext()->IsContextDestroyed()) {
resolver->Reject();
return promise;
}
auto* authenticator =
CredentialManagerProxy::From(script_state)->Authenticator();
authenticator->IsUserVerifyingPlatformAuthenticatorAvailable(WTF::Bind(
&OnIsUserVerifyingComplete,
WTF::Passed(std::make_unique<ScopedPromiseResolver>(resolver))));
return promise;
}
void PublicKeyCredential::getClientExtensionResults(
......
// Copyright 2018 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 "third_party/blink/renderer/modules/credentialmanager/scoped_promise_resolver.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
namespace blink {
ScopedPromiseResolver::ScopedPromiseResolver(ScriptPromiseResolver* resolver)
: resolver_(resolver) {}
ScopedPromiseResolver::~ScopedPromiseResolver() {
if (resolver_)
OnConnectionError();
}
ScriptPromiseResolver* ScopedPromiseResolver::Release() {
return resolver_.Release();
}
void ScopedPromiseResolver::OnConnectionError() {
// The only anticipated reason for a connection error is that the embedder
// does not implement mojom::AuthenticatorImpl.
resolver_->Reject(DOMException::Create(
kNotSupportedError,
"The user agent does not support public key credentials."));
}
} // namespace blink
// Copyright 2018 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_CREDENTIALMANAGER_SCOPED_PROMISE_RESOLVER_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_CREDENTIALMANAGER_SCOPED_PROMISE_RESOLVER_H_
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink {
// Off-heap wrapper that holds a strong reference to a ScriptPromiseResolver.
class ScopedPromiseResolver {
WTF_MAKE_NONCOPYABLE(ScopedPromiseResolver);
public:
explicit ScopedPromiseResolver(ScriptPromiseResolver* resolver);
~ScopedPromiseResolver();
// Releases the owned |resolver_|. This is to be called by the Mojo response
// callback responsible for resolving the corresponding ScriptPromise
//
// If this method is not called before |this| goes of scope, it is assumed
// that a Mojo connection error has occurred, and the response callback was
// never invoked. The Promise will be rejected with an appropriate exception.
ScriptPromiseResolver* Release();
private:
void OnConnectionError();
Persistent<ScriptPromiseResolver> resolver_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_CREDENTIALMANAGER_SCOPED_PROMISE_RESOLVER_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment