Skip to content

Commit

Permalink
tmp
Browse files Browse the repository at this point in the history
  • Loading branch information
devgianlu committed Jan 3, 2025
1 parent 8503d60 commit ae2b77b
Show file tree
Hide file tree
Showing 10 changed files with 973 additions and 2 deletions.
8 changes: 8 additions & 0 deletions Libraries/LibWeb/CredentialManagement/Credential.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

namespace Web::CredentialManagement {

typedef GC::Function<JS::ThrowCompletionOr<GC::Ref<Credential>>(JS::Object const&)> CreateCredentialAlgorithm;

class Credential : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(Credential, Bindings::PlatformObject);
GC_DECLARE_ALLOCATOR(Credential);
Expand All @@ -31,6 +33,12 @@ class Credential : public Bindings::PlatformObject {

virtual String type() = 0;

// https://w3c.github.io/webappsec-credential-management/#algorithm-create-cred
virtual JS::ThrowCompletionOr<Variant<Empty, GC::Ref<Credential>, GC::Ref<CreateCredentialAlgorithm>>> internal_create(URL::Origin const&, CredentialCreationOptions const&, bool)
{
return Empty {};
}

protected:
explicit Credential(JS::Realm&);
virtual void initialize(JS::Realm&) override;
Expand Down
205 changes: 203 additions & 2 deletions Libraries/LibWeb/CredentialManagement/CredentialsContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
*/

#include <LibWeb/CredentialManagement/CredentialsContainer.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Platform/EventLoopPlugin.h>

namespace Web::CredentialManagement {

Expand All @@ -27,9 +30,207 @@ JS::ThrowCompletionOr<GC::Ref<WebIDL::Promise>> CredentialsContainer::store(Cred
return WebIDL::create_rejected_promise(realm(), JS::PrimitiveString::create(realm().vm(), "Not implemented"sv));
}

JS::ThrowCompletionOr<GC::Ref<WebIDL::Promise>> CredentialsContainer::create(CredentialCreationOptions const&)
// https://w3c.github.io/webappsec-credential-management/#algorithm-same-origin-with-ancestors
static bool is_same_origin_with_its_ancestors(HTML::EnvironmentSettingsObject& settings)
{
return WebIDL::create_rejected_promise(realm(), JS::PrimitiveString::create(realm().vm(), "Not implemented"sv));
auto& global = settings.global_object();

// 1. FIXME: If settings’s relevant global object has no associated Document, return false.
// 2. Let document be settings’ relevant global object's associated Document.
auto& document = verify_cast<HTML::Window>(global).associated_document();

// 3. If document has no browsing context, return false.
if (!document.browsing_context())
return false;

// 4. Let origin be settings’ origin.
auto origin = settings.origin();

// 5. Let navigable be document’s node navigable.
auto navigable = document.navigable();

// 6. While navigable has a non-null parent:
while (navigable->parent()) {
// 1. Set navigable to navigable’s parent.
navigable = navigable->parent();

// 2. If navigable’s active document's origin is not same origin with origin, return false.
if (!origin.is_same_origin(navigable->active_document()->origin()))
return false;
}

// 7. Return true.
return true;
}

// https://w3c.github.io/webappsec-credential-management/#credentialrequestoptions-relevant-credential-interface-objects
template<typename OptionsType>
static Vector<GC::Ref<Credential>> relevant_credential_interface_objects(OptionsType const& options)
{
// 1. Let settings be the current settings object.
auto& settings = HTML::current_principal_settings_object();
(void)settings;

// 2. Let relevant interface objects be an empty set.
Vector<GC::Ref<Credential>> interfaces;

// 3. For each optionKey → optionValue of options:
// NOTE: We cannot iterate like the spec says.
// 1. Let credentialInterfaceObject be the Appropriate Interface Object (on settings’ global object) whose Options Member Identifier is optionKey.
// 2. Assert: credentialInterfaceObject’s [[type]] slot equals the Credential Type whose Options Member Identifier is optionKey.
// 3. Append credentialInterfaceObject to relevant interface objects.

#define APPEND_CREDENTIAL_INTERFACE_OBJECT(key, type_) \
if (options.key.has_value()) { \
auto credential_interface_object = type_::create(settings.realm()); \
VERIFY(credential_interface_object->type() == #key); \
interfaces.append(move(credential_interface_object)); \
}

// https://w3c.github.io/webappsec-credential-management/#credential-type-registry-appropriate-interface-object
APPEND_CREDENTIAL_INTERFACE_OBJECT(password, PasswordCredential);
APPEND_CREDENTIAL_INTERFACE_OBJECT(federated, FederatedCredential);
// TODO: digital
// TODO: identity
// TODO: otp
// TODO: publicKey

// 4. Return relevant interface objects.
return interfaces;
}

// https://w3c.github.io/webappsec-credential-management/#algorithm-create
JS::ThrowCompletionOr<GC::Ref<WebIDL::Promise>> CredentialsContainer::create(CredentialCreationOptions const& options)
{
// 1. Let settings be the current settings object.
auto& settings = HTML::current_principal_settings_object();

// 2. Assert: settings is a secure context.
VERIFY(HTML::is_secure_context(settings));

// 3. Let global be settings’ global object.
auto& global = settings.global_object();

// 4. Let document be the relevant global object's associated Document.
auto& document = verify_cast<HTML::Window>(global).associated_document();

// 5. If document is not fully active, then return a promise rejected with an "InvalidStateError" DOMException.
if (!document.is_fully_active())
return WebIDL::create_rejected_promise_from_exception(realm(), WebIDL::InvalidStateError::create(realm(), "Document is not fully active"_string));

// 6. Let sameOriginWithAncestors be true if the current settings object is same-origin with its ancestors, and false otherwise.
auto same_origin_with_ancestors = is_same_origin_with_its_ancestors(settings);
(void)same_origin_with_ancestors; // FIXME

// 7. Let interfaces be the set of options’ relevant credential interface objects.
auto interfaces = relevant_credential_interface_objects(options);

// 8. Return a promise rejected with NotSupportedError if any of the following statements are true:
// FIXME: 1. global does not have an associated Document.
// 2. interfaces’ size is greater than 1.
if (interfaces.size() > 1)
return WebIDL::create_rejected_promise_from_exception(realm(), WebIDL::NotSupportedError::create(realm(), "Too many crendetial types"_string));

// 9. For each interface in interfaces:
for (auto& interface : interfaces) {
// 1. Let permission be the interface’s [[type]] Create Permissions Policy.
// 2. If permission is null, continue.
// 3. If document is not allowed to use permission, return a promise rejected with a "NotAllowedError" DOMException.

// https://w3c.github.io/webappsec-credential-management/#credential-type-registry-create-permissions-policy
if (interface->type() == "public-key") {
// TODO: https://w3c.github.io/webauthn/#publickey-credentials-create-feature
VERIFY_NOT_REACHED();
}
}

// 10. If options.signal is aborted, then return a promise rejected with options.signal’s abort reason.
if (options.signal && options.signal->aborted())
return WebIDL::create_rejected_promise(realm(), options.signal->reason());

// NOTE: The spec does not mention this check
if (interfaces.size() < 1)
return WebIDL::create_rejected_promise_from_exception(realm(), WebIDL::NotSupportedError::create(realm(), "No crendetial types"_string));

// 11. Let type be interfaces[0]'s [[type]].
auto type = interfaces[0]->type();

// 12. If settings’ active credential types contains type, return a promise rejected with a "NotAllowedError" DOMException.
if (settings.active_credential_types().contains_slow(type))
return WebIDL::create_rejected_promise_from_exception(realm(), WebIDL::NotAllowedError::create(realm(), "Credential type is not allowed"_string));

// 13. Append type to settings’ active credential types.
settings.active_credential_types().append(type);

// 14. Let origin be settings’s origin.
auto origin = settings.origin();

// 15. Let p be a new promise.
auto promise = WebIDL::create_promise(realm());

// 16. Run the following steps in parallel:
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm().heap(), [this, promise = GC::Root(promise), &global, &document, interfaces = move(interfaces), &origin, &options, same_origin_with_ancestors] {
HTML::TemporaryExecutionContext execution_context { realm(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes };

// 1. Let r be the result of executing interfaces[0]'s [[Create]](origin, options, sameOriginWithAncestors)
// internal method on origin, options, and sameOriginWithAncestors.
auto maybe_r = interfaces[0]->internal_create(origin, options, same_origin_with_ancestors);
// If that threw an exception:
if (maybe_r.is_error()) {
// 1. Let e be the thrown exception.
auto e = maybe_r.error_value();
// 2. Queue a task on global’s DOM manipulation task source to run the following substeps:
queue_global_task(HTML::Task::Source::DOMManipulation, global, GC::create_function(document.heap(), [&] {
// 1. Reject p with e.
WebIDL::reject_promise(realm(), *promise, e);
}));
// 3. Terminate these substeps.
return;
}

auto r = maybe_r.release_value();

// 2. If r is a Credential or null, resolve p with r, and terminate these substeps.
if (r.has<Empty>()) {
WebIDL::resolve_promise(realm(), *promise, JS::js_null());
return;
}
if (r.has<GC::Ref<Credential>>()) {
auto& credential = r.get<GC::Ref<Credential>>();
WebIDL::resolve_promise(realm(), *promise, credential);
return;
}

// 3. Assert: r is an algorithm (as defined in §2.2.1.4 [[Create]] internal method).
VERIFY(r.has<GC::Ref<CreateCredentialAlgorithm>>());

// 4. Queue a task on global’s DOM manipulation task source to run the following substeps:
queue_global_task(HTML::Task::Source::DOMManipulation, global, GC::create_function(document.heap(), [&] {
auto& r_algo = r.get<GC::Ref<CreateCredentialAlgorithm>>();

// 1. Resolve p with the result of promise-calling r given global.
auto maybe_result = r_algo->function()(global);
if (maybe_result.is_error()) {
WebIDL::reject_promise(realm(), *promise, maybe_result.error_value());
return;
}

auto& result = maybe_result.value();
WebIDL::resolve_promise(realm(), *promise, result);
}));
}));

// 17. React to p:
auto on_completion = GC::create_function(realm().heap(), [&settings, &type](JS::Value) -> WebIDL::ExceptionOr<JS::Value> {
// 1. Remove type from settings’ active credential types.
settings.active_credential_types().remove_first_matching([&](auto& v) { return v == type; });

return JS::js_undefined();
});
WebIDL::react_to_promise(*promise, on_completion, on_completion);

// 18. Return p.
return promise;
}

JS::ThrowCompletionOr<GC::Ref<WebIDL::Promise>> CredentialsContainer::prevent_silent_access()
Expand Down
2 changes: 2 additions & 0 deletions Libraries/LibWeb/CredentialManagement/FederatedCredential.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class FederatedCredential final : public Credential {

String type() override { return "federated"_string; }

//JS::ThrowCompletionOr<Variant<GC::Ref<Credential>, CreateCredentialAlgorithm>> internal_create(const URL::Origin&, CredentialCreationOptions const&, bool) override;

private:
explicit FederatedCredential(JS::Realm&);
virtual void initialize(JS::Realm&) override;
Expand Down
2 changes: 2 additions & 0 deletions Libraries/LibWeb/CredentialManagement/PasswordCredential.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class PasswordCredential final : public Credential {

String type() override { return "password"_string; }

//JS::ThrowCompletionOr<Variant<GC::Ref<Credential>, CreateCredentialAlgorithm>> internal_create(const URL::Origin&, CredentialCreationOptions const&, bool) override;

private:
explicit PasswordCredential(JS::Realm&);
virtual void initialize(JS::Realm&) override;
Expand Down
6 changes: 6 additions & 0 deletions Libraries/LibWeb/HTML/Scripting/Environments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -582,4 +582,10 @@ GC::Ref<StorageAPI::StorageManager> EnvironmentSettingsObject::storage_manager()
return *m_storage_manager;
}

// https://w3c.github.io/webappsec-credential-management/#active-credential-types
Vector<String> EnvironmentSettingsObject::active_credential_types() const
{
return m_active_credential_types;
}

}
5 changes: 5 additions & 0 deletions Libraries/LibWeb/HTML/Scripting/Environments.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ struct EnvironmentSettingsObject : public Environment {

GC::Ref<StorageAPI::StorageManager> storage_manager();

Vector<String> active_credential_types() const;

[[nodiscard]] bool discarded() const { return m_discarded; }
void set_discarded(bool b) { m_discarded = b; }

Expand All @@ -124,6 +126,9 @@ struct EnvironmentSettingsObject : public Environment {
// Each environment settings object has an associated StorageManager object.
GC::Ptr<StorageAPI::StorageManager> m_storage_manager;

// https://w3c.github.io/webappsec-credential-management/#active-credential-types
Vector<String> m_active_credential_types;

// https://w3c.github.io/ServiceWorker/#service-worker-client-discarded-flag
// A service worker client has an associated discarded flag. It is initially unset.
bool m_discarded { false };
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Harness status: OK

Found 12 tests

1 Pass
11 Fail
Fail Bad rp: rp missing
Fail Bad rp: rp null
Fail Bad rp: rp is string
Fail Bad rp: rp is empty object
Fail Bad rp: id is null
Fail Bad rp: id is empty String
Fail Bad rp: id is invalid domain (has space)
Fail Bad rp: id is invalid domain (starts with dash)
Fail Bad rp: id is invalid domain (starts with number)
Fail Bad rp id: id is host + port
Fail rp missing name
Pass Clean up the test environment
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,11 @@ window.test_driver_internal.get_computed_label = async function(element) {
window.test_driver_internal.get_computed_role = async function(element) {
return await window.internals.getComputedRole(element);
};

window.test_driver_internal.add_virtual_authenticator = function(config, context=null) {
return Promise.resolve("hello")
}

window.test_driver_internal.remove_virtual_authenticator = function(config, context=null) {
return Promise.resolve("hello")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>WebAuthn navigator.credentials.create() rp Tests</title>
<meta name="timeout" content="long">
<link rel="author" title="Adam Powers" href="mailto:[email protected]">
<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="../resources/testdriver.js"></script>
<script src="../resources/testdriver-vendor.js"></script>
<script src=helpers.js></script>
<body></body>
<script>
standardSetup(function() {
"use strict";

// rp bad values
new CreateCredentialsTest({path: "options.publicKey.rp", value: undefined}).runTest("Bad rp: rp missing", TypeError);
new CreateCredentialsTest({ path: "options.publicKey.rp", value: null }).runTest("Bad rp: rp null", TypeError);
new CreateCredentialsTest("options.publicKey.rp", "hi mom").runTest("Bad rp: rp is string", TypeError);
new CreateCredentialsTest("options.publicKey.rp", {}).runTest("Bad rp: rp is empty object", TypeError);

// // rp.id
new CreateCredentialsTest("options.publicKey.rp.id", null).runTest("Bad rp: id is null", "SecurityError");
new CreateCredentialsTest("options.publicKey.rp.id", "").runTest("Bad rp: id is empty String", "SecurityError");
new CreateCredentialsTest("options.publicKey.rp.id", "invalid domain.com").runTest("Bad rp: id is invalid domain (has space)", "SecurityError");
new CreateCredentialsTest("options.publicKey.rp.id", "-invaliddomain.com").runTest("Bad rp: id is invalid domain (starts with dash)", "SecurityError");
new CreateCredentialsTest("options.publicKey.rp.id", "0invaliddomain.com").runTest("Bad rp: id is invalid domain (starts with number)", "SecurityError");

let hostAndPort = window.location.host;
if (!hostAndPort.match(/:\d+$/)) {
hostAndPort += ":443";
}
new CreateCredentialsTest("options.publicKey.rp.id", hostAndPort).runTest("Bad rp id: id is host + port", "SecurityError");

// // rp.name
new CreateCredentialsTest({path: "options.publicKey.rp.name", value: undefined}).runTest("rp missing name", TypeError);
});

/* JSHINT */
/* globals standardSetup, CreateCredentialsTest */
</script>
Loading

0 comments on commit ae2b77b

Please sign in to comment.