[WebAuthn] Implement SPI to tell UI clients to select assertion responses
authorjiewen_tan@apple.com <jiewen_tan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 14 Jan 2020 21:37:23 +0000 (21:37 +0000)
committerjiewen_tan@apple.com <jiewen_tan@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 14 Jan 2020 21:37:23 +0000 (21:37 +0000)
https://bugs.webkit.org/show_bug.cgi?id=206112
<rdar://problem/58495733>

Reviewed by Alex Christensen.

Source/WebKit:

Covered by API tests.

This patch adds a new delegate method in _WKWebAuthenticationPanelDelegate to tell UI clients to
select an assertion response when multiple are returned from an authenticator. Here is the SPI:
@protocol _WKWebAuthenticationPanelDelegate <NSObject>
@optional
...
- (void)panel:(_WKWebAuthenticationPanel *)panel selectAssertionResponse:(NSArray < _WKWebAuthenticationAssertionResponse *> *)responses completionHandler:(void (^)(_WKWebAuthenticationAssertionResponse *))completionHandler;
...
@end

A new interface is introduced to represent an authenticator assertion response:
@interface _WKWebAuthenticationAssertionResponse : NSObject
@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, copy) NSString *displayName;
@property (nonatomic, readonly, copy) NSData *userHandle;
@end
Only members that can assist users to make a decision are exposed to UI clients.

* Shared/API/APIObject.h:
* Shared/Cocoa/APIObject.mm:
(API::Object::newObject):
* Sources.txt:
* SourcesCocoa.txt:
* UIProcess/API/APIWebAuthenticationAssertionResponse.cpp: Copied from Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h.
(API::WebAuthenticationAssertionResponse::create):
(API::WebAuthenticationAssertionResponse::WebAuthenticationAssertionResponse):
* UIProcess/API/APIWebAuthenticationAssertionResponse.h: Copied from Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h.
* UIProcess/API/Cocoa/_WKWebAuthenticationAssertionResponse.h: Copied from Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h.
* UIProcess/API/Cocoa/_WKWebAuthenticationAssertionResponse.mm: Copied from Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h.
(-[_WKWebAuthenticationAssertionResponse dealloc]):
(-[_WKWebAuthenticationAssertionResponse name]):
(-[_WKWebAuthenticationAssertionResponse displayName]):
(-[_WKWebAuthenticationAssertionResponse userHandle]):
(-[_WKWebAuthenticationAssertionResponse _apiObject]):
* UIProcess/API/Cocoa/_WKWebAuthenticationAssertionResponseInternal.h: Copied from Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h.
* UIProcess/API/Cocoa/_WKWebAuthenticationPanel.h:
* UIProcess/API/Cocoa/_WKWebAuthenticationPanel.mm:
* UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h:
* UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.mm:
(WebKit::WebAuthenticationPanelClient::WebAuthenticationPanelClient):
(WebKit::WebAuthenticationPanelClient::selectAssertionResponses const):
* WebKit.xcodeproj/project.pbxproj:

Tools:

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm:
(-[TestWebAuthenticationPanelDelegate panel:selectAssertionResponses:completionHandler:]):
(TestWebKitAPI::TEST):
* TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid-multiple-accounts.html: Added.
* TestWebKitAPI/Tests/WebKitCocoa/web-authentication-make-credential-hid.html:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@254533 268f45cc-cd09-0410-ab3c-d52691b4dbfc

29 files changed:
Source/WebCore/Modules/webauthn/AuthenticatorAssertionResponse.h
Source/WebKit/ChangeLog
Source/WebKit/Shared/API/APIObject.h
Source/WebKit/Shared/Cocoa/APIObject.mm
Source/WebKit/Sources.txt
Source/WebKit/SourcesCocoa.txt
Source/WebKit/UIProcess/API/APIResourceLoadClient.h
Source/WebKit/UIProcess/API/APIWebAuthenticationAssertionResponse.cpp [new file with mode: 0644]
Source/WebKit/UIProcess/API/APIWebAuthenticationAssertionResponse.h [new file with mode: 0644]
Source/WebKit/UIProcess/API/APIWebAuthenticationPanelClient.h
Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationAssertionResponse.h [new file with mode: 0644]
Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationAssertionResponse.mm [new file with mode: 0644]
Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationAssertionResponseInternal.h [new file with mode: 0644]
Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationPanel.h
Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationPanel.mm
Source/WebKit/UIProcess/Cocoa/ResourceLoadDelegate.h
Source/WebKit/UIProcess/Cocoa/ResourceLoadDelegate.mm
Source/WebKit/UIProcess/WebAuthentication/Authenticator.h
Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.cpp
Source/WebKit/UIProcess/WebAuthentication/AuthenticatorManager.h
Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h
Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.mm
Source/WebKit/UIProcess/WebAuthentication/fido/CtapAuthenticator.cpp
Source/WebKit/WebKit.xcodeproj/project.pbxproj
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm
Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid-multiple-accounts.html [new file with mode: 0644]
Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-make-credential-hid.html

index dc4c803..d70a9cc 100644 (file)
@@ -42,9 +42,9 @@ public:
     ArrayBuffer* userHandle() const { return m_userHandle.get(); }
 
     void setName(const String& name) { m_name = name; }
-    String name() const { return m_name; }
+    const String& name() const { return m_name; }
     void setDisplayName(const String& displayName) { m_displayName = displayName; }
-    String displayName() const { return m_displayName; }
+    const String& displayName() const { return m_displayName; }
     void setNumberOfCredentials(size_t numberOfCredentials) { m_numberOfCredentials = numberOfCredentials; }
     size_t numberOfCredentials() const { return m_numberOfCredentials; }
 
index 30a74c0..f6eb212 100644 (file)
@@ -1,3 +1,55 @@
+2020-01-10  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthn] Implement SPI to tell UI clients to select assertion responses
+        https://bugs.webkit.org/show_bug.cgi?id=206112
+        <rdar://problem/58495733>
+
+        Reviewed by Alex Christensen.
+
+        Covered by API tests.
+
+        This patch adds a new delegate method in _WKWebAuthenticationPanelDelegate to tell UI clients to
+        select an assertion response when multiple are returned from an authenticator. Here is the SPI:
+        @protocol _WKWebAuthenticationPanelDelegate <NSObject>
+        @optional
+        ...
+        - (void)panel:(_WKWebAuthenticationPanel *)panel selectAssertionResponse:(NSArray < _WKWebAuthenticationAssertionResponse *> *)responses completionHandler:(void (^)(_WKWebAuthenticationAssertionResponse *))completionHandler;
+        ...
+        @end
+
+        A new interface is introduced to represent an authenticator assertion response:
+        @interface _WKWebAuthenticationAssertionResponse : NSObject
+        @property (nonatomic, readonly, copy) NSString *name;
+        @property (nonatomic, readonly, copy) NSString *displayName;
+        @property (nonatomic, readonly, copy) NSData *userHandle;
+        @end
+        Only members that can assist users to make a decision are exposed to UI clients.
+
+        * Shared/API/APIObject.h:
+        * Shared/Cocoa/APIObject.mm:
+        (API::Object::newObject):
+        * Sources.txt:
+        * SourcesCocoa.txt:
+        * UIProcess/API/APIWebAuthenticationAssertionResponse.cpp: Copied from Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h.
+        (API::WebAuthenticationAssertionResponse::create):
+        (API::WebAuthenticationAssertionResponse::WebAuthenticationAssertionResponse):
+        * UIProcess/API/APIWebAuthenticationAssertionResponse.h: Copied from Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h.
+        * UIProcess/API/Cocoa/_WKWebAuthenticationAssertionResponse.h: Copied from Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h.
+        * UIProcess/API/Cocoa/_WKWebAuthenticationAssertionResponse.mm: Copied from Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h.
+        (-[_WKWebAuthenticationAssertionResponse dealloc]):
+        (-[_WKWebAuthenticationAssertionResponse name]):
+        (-[_WKWebAuthenticationAssertionResponse displayName]):
+        (-[_WKWebAuthenticationAssertionResponse userHandle]):
+        (-[_WKWebAuthenticationAssertionResponse _apiObject]):
+        * UIProcess/API/Cocoa/_WKWebAuthenticationAssertionResponseInternal.h: Copied from Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h.
+        * UIProcess/API/Cocoa/_WKWebAuthenticationPanel.h:
+        * UIProcess/API/Cocoa/_WKWebAuthenticationPanel.mm:
+        * UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.h:
+        * UIProcess/WebAuthentication/Cocoa/WebAuthenticationPanelClient.mm:
+        (WebKit::WebAuthenticationPanelClient::WebAuthenticationPanelClient):
+        (WebKit::WebAuthenticationPanelClient::selectAssertionResponses const):
+        * WebKit.xcodeproj/project.pbxproj:
+
 2020-01-14  Ross Kirsling  <ross.kirsling@sony.com>
 
         WebPasteboardProxyWPE should be WebPasteboardProxyLibWPE
index fdb81f4..8da13c4 100644 (file)
@@ -183,6 +183,7 @@ public:
 #endif
 
 #if ENABLE(WEB_AUTHN)
+        WebAuthenticationAssertionResponse,
         WebAuthenticationPanel,
 #endif
 
index 642f6ef..bf24db6 100644 (file)
@@ -88,6 +88,7 @@
 #import "_WKUserInitiatedActionInternal.h"
 #import "_WKUserStyleSheetInternal.h"
 #import "_WKVisitedLinkStoreInternal.h"
+#import "_WKWebAuthenticationAssertionResponseInternal.h"
 #import "_WKWebAuthenticationPanelInternal.h"
 #import "_WKWebsiteDataStoreConfigurationInternal.h"
 
@@ -389,6 +390,9 @@ void* Object::newObject(size_t size, Type type)
     case Type::WebAuthenticationPanel:
         wrapper = [_WKWebAuthenticationPanel alloc];
         break;
+    case Type::WebAuthenticationAssertionResponse:
+        wrapper = [_WKWebAuthenticationAssertionResponse alloc];
+        break;
 #endif
 
     case Type::BundleFrame:
index 606f039..8f8a562 100644 (file)
@@ -341,6 +341,7 @@ UIProcess/API/APIURLSchemeTask.cpp
 UIProcess/API/APIUserContentWorld.cpp
 UIProcess/API/APIUserScript.cpp
 UIProcess/API/APIUserStyleSheet.cpp
+UIProcess/API/APIWebAuthenticationAssertionResponse.cpp
 UIProcess/API/APIWebAuthenticationPanel.cpp
 UIProcess/API/APIWebsitePolicies.cpp
 UIProcess/API/APIWindowFeatures.cpp
index 394c670..b73dd81 100644 (file)
@@ -278,6 +278,7 @@ UIProcess/API/Cocoa/_WKUserContentWorld.mm
 UIProcess/API/Cocoa/_WKUserInitiatedAction.mm
 UIProcess/API/Cocoa/_WKUserStyleSheet.mm
 UIProcess/API/Cocoa/_WKVisitedLinkStore.mm
+UIProcess/API/Cocoa/_WKWebAuthenticationAssertionResponse.mm
 UIProcess/API/Cocoa/_WKWebAuthenticationPanel.mm
 UIProcess/API/Cocoa/_WKWebsiteDataSize.mm
 UIProcess/API/Cocoa/_WKWebsiteDataStore.mm
index 2181d8d..a30dd95 100644 (file)
 
 #pragma once
 
+#include <WebCore/ResourceError.h>
+#include <WebCore/ResourceRequest.h>
+#include <WebCore/ResourceResponse.h>
+
 namespace WebKit {
 class AuthenticationChallengeProxy;
 struct ResourceLoadInfo;
diff --git a/Source/WebKit/UIProcess/API/APIWebAuthenticationAssertionResponse.cpp b/Source/WebKit/UIProcess/API/APIWebAuthenticationAssertionResponse.cpp
new file mode 100644 (file)
index 0000000..c7976ff
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "APIWebAuthenticationAssertionResponse.h"
+
+#if ENABLE(WEB_AUTHN)
+
+#include "APIData.h"
+
+namespace API {
+using namespace WebCore;
+
+Ref<WebAuthenticationAssertionResponse> WebAuthenticationAssertionResponse::create(Ref<WebCore::AuthenticatorAssertionResponse>&& response)
+{
+    return adoptRef(*new WebAuthenticationAssertionResponse(WTFMove(response)));
+}
+
+WebAuthenticationAssertionResponse::WebAuthenticationAssertionResponse(Ref<WebCore::AuthenticatorAssertionResponse>&& response)
+    : m_response(WTFMove(response))
+{
+}
+
+WebAuthenticationAssertionResponse::~WebAuthenticationAssertionResponse() = default;
+
+RefPtr<Data> WebAuthenticationAssertionResponse::userHandle() const
+{
+    RefPtr<API::Data> data;
+    if (auto* userHandle = m_response->userHandle()) {
+        userHandle->ref();
+        data = API::Data::createWithoutCopying(reinterpret_cast<unsigned char*>(userHandle->data()), userHandle->byteLength(), [] (unsigned char*, const void* data) {
+            static_cast<ArrayBuffer*>(const_cast<void*>(data))->deref();
+        }, userHandle);
+    }
+    return data;
+}
+
+} // namespace API
+
+#endif // ENABLE(WEB_AUTHN)
diff --git a/Source/WebKit/UIProcess/API/APIWebAuthenticationAssertionResponse.h b/Source/WebKit/UIProcess/API/APIWebAuthenticationAssertionResponse.h
new file mode 100644 (file)
index 0000000..4176208
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(WEB_AUTHN)
+
+#include "APIObject.h"
+#include <WebCore/AuthenticatorAssertionResponse.h>
+
+namespace API {
+
+class Data;
+
+class WebAuthenticationAssertionResponse final : public ObjectImpl<Object::Type::WebAuthenticationAssertionResponse> {
+public:
+    static Ref<WebAuthenticationAssertionResponse> create(Ref<WebCore::AuthenticatorAssertionResponse>&&);
+    ~WebAuthenticationAssertionResponse();
+
+    const WTF::String& name() const { return m_response->name(); }
+    const WTF::String& displayName() const { return m_response->displayName(); }
+    RefPtr<Data> userHandle() const;
+
+    const WebCore::AuthenticatorAssertionResponse& response() { return m_response.get(); }
+
+private:
+    WebAuthenticationAssertionResponse(Ref<WebCore::AuthenticatorAssertionResponse>&&);
+
+    Ref<WebCore::AuthenticatorAssertionResponse> m_response;
+};
+
+} // namespace API
+
+#endif // ENABLE(WEB_AUTHN)
index 0062f9a..7010d78 100644 (file)
@@ -50,8 +50,8 @@ public:
 
     virtual void updatePanel(WebKit::WebAuthenticationStatus) const { }
     virtual void dismissPanel(WebKit::WebAuthenticationResult) const { }
-    virtual void selectAssertionResponses(const HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>& responses, CompletionHandler<void(const Ref<WebCore::AuthenticatorAssertionResponse>&)>&& completionHandler) const { completionHandler(*responses.begin()); }
     virtual void requestPin(uint64_t, CompletionHandler<void(const WTF::String&)>&& completionHandler) const { completionHandler(emptyString()); }
+    virtual void selectAssertionResponse(const HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>& responses, CompletionHandler<void(const WebCore::AuthenticatorAssertionResponse&)>&& completionHandler) const { ASSERT(!responses.isEmpty()); completionHandler(*responses.begin()); }
 };
 
 } // namespace API
diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationAssertionResponse.h b/Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationAssertionResponse.h
new file mode 100644 (file)
index 0000000..412f5af
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#import <WebKit/WKFoundation.h>
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+WK_CLASS_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA))
+@interface _WKWebAuthenticationAssertionResponse : NSObject
+
+@property (nonatomic, readonly, copy) NSString *name;
+@property (nonatomic, readonly, copy) NSString *displayName;
+@property (nonatomic, readonly, copy) NSData *userHandle;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationAssertionResponse.mm b/Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationAssertionResponse.mm
new file mode 100644 (file)
index 0000000..971f236
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+#import "_WKWebAuthenticationAssertionResponseInternal.h"
+
+#import "WKNSData.h"
+
+@implementation _WKWebAuthenticationAssertionResponse
+
+#if ENABLE(WEB_AUTHN)
+
+- (void)dealloc
+{
+    _response->~WebAuthenticationAssertionResponse();
+
+    [super dealloc];
+}
+
+- (NSString *)name
+{
+    return _response->name();
+}
+
+- (NSString *)displayName
+{
+    return _response->displayName();
+}
+
+- (NSData *)userHandle
+{
+    return wrapper(_response->userHandle());
+}
+
+#pragma mark WKObject protocol implementation
+
+- (API::Object&)_apiObject
+{
+    return *_response;
+}
+#endif
+
+@end
diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationAssertionResponseInternal.h b/Source/WebKit/UIProcess/API/Cocoa/_WKWebAuthenticationAssertionResponseInternal.h
new file mode 100644 (file)
index 0000000..ef3600f
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#import "_WKWebAuthenticationAssertionResponse.h"
+
+#if ENABLE(WEB_AUTHN)
+
+#import "APIWebAuthenticationAssertionResponse.h"
+#import "WKObject.h"
+
+namespace WebKit {
+
+template<> struct WrapperTraits<API::WebAuthenticationAssertionResponse> {
+    using WrapperClass = _WKWebAuthenticationAssertionResponse;
+};
+
+}
+
+@interface _WKWebAuthenticationAssertionResponse () <WKObject> {
+@package
+    API::ObjectStorage<API::WebAuthenticationAssertionResponse> _response;
+}
+@end
+
+#endif // ENABLE(WEB_AUTHN)
index 8680623..431c633 100644 (file)
@@ -31,6 +31,7 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
+@class _WKWebAuthenticationAssertionResponse;
 @class _WKWebAuthenticationPanel;
 
 typedef NS_ENUM(NSInteger, _WKWebAuthenticationPanelResult) {
@@ -69,6 +70,7 @@ typedef NS_ENUM(NSInteger, _WKWebAuthenticationType) {
 - (void)panel:(_WKWebAuthenticationPanel *)panel updateWebAuthenticationPanel:(_WKWebAuthenticationPanelUpdate)update WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
 - (void)panel:(_WKWebAuthenticationPanel *)panel dismissWebAuthenticationPanelWithResult:(_WKWebAuthenticationResult)result WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
 - (void)panel:(_WKWebAuthenticationPanel *)panel requestPINWithRemainingRetries:(NSUInteger)retries completionHandler:(void (^)(NSString *))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
+- (void)panel:(_WKWebAuthenticationPanel *)panel selectAssertionResponse:(NSArray < _WKWebAuthenticationAssertionResponse *> *)responses completionHandler:(void (^)(_WKWebAuthenticationAssertionResponse *))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
 
 @end
 
index 4474bac..2eb31e8 100644 (file)
  */
 
 #import "config.h"
-#import "WebAuthenticationPanelClient.h"
 #import "_WKWebAuthenticationPanelInternal.h"
-#import <WebCore/WebAuthenticationConstants.h>
 
+#import "WebAuthenticationPanelClient.h"
+#import <WebCore/WebAuthenticationConstants.h>
 #import <wtf/RetainPtr.h>
 
 @implementation _WKWebAuthenticationPanel {
index e835304..a47b292 100644 (file)
@@ -29,6 +29,8 @@
 #import "WKFoundation.h"
 #import <wtf/WeakObjCPtr.h>
 
+@class WKWebView;
+
 @protocol _WKResourceLoadDelegate;
 
 namespace API {
index 6b7800b..d426285 100644 (file)
@@ -26,6 +26,8 @@
 #import "config.h"
 #import "ResourceLoadDelegate.h"
 
+#import "AuthenticationChallengeProxy.h"
+#import "WKNSURLAuthenticationChallenge.h"
 #import "_WKResourceLoadDelegate.h"
 #import "_WKResourceLoadInfoInternal.h"
 
@@ -80,7 +82,7 @@ void ResourceLoadDelegate::ResourceLoadClient::didSendRequest(WebKit::ResourceLo
     if (!delegate)
         return;
 
-    [delegate webView:m_resourceLoadDelegate.m_webView.get().get() resourceLoad:wrapper(API::ResourceLoadInfo::create(WTFMove(loadInfo)).get()) didSendRequest:request.nsURLRequest(HTTPBodyUpdatePolicy::DoNotUpdateHTTPBody)];
+    [delegate webView:m_resourceLoadDelegate.m_webView.get().get() resourceLoad:wrapper(API::ResourceLoadInfo::create(WTFMove(loadInfo)).get()) didSendRequest:request.nsURLRequest(WebCore::HTTPBodyUpdatePolicy::DoNotUpdateHTTPBody)];
 }
 
 void ResourceLoadDelegate::ResourceLoadClient::didPerformHTTPRedirection(WebKit::ResourceLoadInfo&& loadInfo, WebCore::ResourceResponse&& response, WebCore::ResourceRequest&& request) const
@@ -92,7 +94,7 @@ void ResourceLoadDelegate::ResourceLoadClient::didPerformHTTPRedirection(WebKit:
     if (!delegate)
         return;
 
-    [delegate webView:m_resourceLoadDelegate.m_webView.get().get() resourceLoad:wrapper(API::ResourceLoadInfo::create(WTFMove(loadInfo)).get()) didPerformHTTPRedirection:response.nsURLResponse() newRequest:request.nsURLRequest(HTTPBodyUpdatePolicy::DoNotUpdateHTTPBody)];
+    [delegate webView:m_resourceLoadDelegate.m_webView.get().get() resourceLoad:wrapper(API::ResourceLoadInfo::create(WTFMove(loadInfo)).get()) didPerformHTTPRedirection:response.nsURLResponse() newRequest:request.nsURLRequest(WebCore::HTTPBodyUpdatePolicy::DoNotUpdateHTTPBody)];
 }
 
 void ResourceLoadDelegate::ResourceLoadClient::didReceiveChallenge(WebKit::ResourceLoadInfo&& loadInfo, WebKit::AuthenticationChallengeProxy& challenge) const
index 32fc7d2..d16b17d 100644 (file)
@@ -51,8 +51,8 @@ public:
         virtual void respondReceived(Respond&&) = 0;
         virtual void downgrade(Authenticator* id, Ref<Authenticator>&& downgradedAuthenticator) = 0;
         virtual void authenticatorStatusUpdated(WebAuthenticationStatus) = 0;
-        virtual void selectAssertionResponses(const HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>&, CompletionHandler<void(const Ref<WebCore::AuthenticatorAssertionResponse>&)>&&) = 0;
         virtual void requestPin(uint64_t retries, CompletionHandler<void(const WTF::String&)>&&) = 0;
+        virtual void selectAssertionResponse(const HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>&, CompletionHandler<void(const WebCore::AuthenticatorAssertionResponse&)>&&) = 0;
     };
 
     virtual ~Authenticator() = default;
index 9cd4547..3db63ef 100644 (file)
@@ -273,16 +273,16 @@ void AuthenticatorManager::authenticatorStatusUpdated(WebAuthenticationStatus st
         panel->client().updatePanel(status);
 }
 
-void AuthenticatorManager::selectAssertionResponses(const HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>& responses, CompletionHandler<void(const Ref<WebCore::AuthenticatorAssertionResponse>&)>&& completionHandler)
+void AuthenticatorManager::requestPin(uint64_t retries, CompletionHandler<void(const WTF::String&)>&& completionHandler)
 {
     if (auto* panel = m_pendingRequestData.panel.get())
-        panel->client().selectAssertionResponses(responses, WTFMove(completionHandler));
+        panel->client().requestPin(retries, WTFMove(completionHandler));
 }
 
-void AuthenticatorManager::requestPin(uint64_t retries, CompletionHandler<void(const WTF::String&)>&& completionHandler)
+void AuthenticatorManager::selectAssertionResponse(const HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>& responses, CompletionHandler<void(const WebCore::AuthenticatorAssertionResponse&)>&& completionHandler)
 {
     if (auto* panel = m_pendingRequestData.panel.get())
-        panel->client().requestPin(retries, WTFMove(completionHandler));
+        panel->client().selectAssertionResponse(responses, WTFMove(completionHandler));
 }
 
 UniqueRef<AuthenticatorTransportService> AuthenticatorManager::createService(AuthenticatorTransport transport, AuthenticatorTransportService::Observer& observer) const
index 9fd523d..a9b6293 100644 (file)
@@ -81,8 +81,8 @@ private:
     void respondReceived(Respond&&) final;
     void downgrade(Authenticator* id, Ref<Authenticator>&& downgradedAuthenticator) final;
     void authenticatorStatusUpdated(WebAuthenticationStatus) final;
-    void selectAssertionResponses(const HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>&, CompletionHandler<void(const Ref<WebCore::AuthenticatorAssertionResponse>&)>&&) final;
     void requestPin(uint64_t retries, CompletionHandler<void(const WTF::String&)>&&) final;
+    void selectAssertionResponse(const HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>&, CompletionHandler<void(const WebCore::AuthenticatorAssertionResponse&)>&&) final;
 
     // Overriden by MockAuthenticatorManager.
     virtual UniqueRef<AuthenticatorTransportService> createService(WebCore::AuthenticatorTransport, AuthenticatorTransportService::Observer&) const;
index 1499466..fafaa81 100644 (file)
@@ -50,6 +50,7 @@ private:
     void updatePanel(WebAuthenticationStatus) const final;
     void dismissPanel(WebAuthenticationResult) const final;
     void requestPin(uint64_t, CompletionHandler<void(const WTF::String&)>&&) const final;
+    void selectAssertionResponse(const HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>&, CompletionHandler<void(const WebCore::AuthenticatorAssertionResponse&)>&&) const final;
 
     _WKWebAuthenticationPanel *m_panel;
     WeakObjCPtr<id <_WKWebAuthenticationPanelDelegate> > m_delegate;
@@ -58,6 +59,7 @@ private:
         bool panelUpdateWebAuthenticationPanel : 1;
         bool panelDismissWebAuthenticationPanelWithResult : 1;
         bool panelRequestPinWithRemainingRetriesCompletionHandler : 1;
+        bool panelselectAssertionResponseCompletionHandler : 1;
     } m_delegateMethods;
 };
 
index 7f157bd..0c1bd4f 100644 (file)
 
 #if ENABLE(WEB_AUTHN)
 
+#import "APIWebAuthenticationAssertionResponse.h"
 #import "CompletionHandlerCallChecker.h"
 #import "WebAuthenticationFlags.h"
+#import "_WKWebAuthenticationAssertionResponseInternal.h"
 #import "_WKWebAuthenticationPanel.h"
 #import <wtf/BlockPtr.h>
 #import <wtf/RunLoop.h>
@@ -43,6 +45,7 @@ WebAuthenticationPanelClient::WebAuthenticationPanelClient(_WKWebAuthenticationP
     m_delegateMethods.panelUpdateWebAuthenticationPanel = [delegate respondsToSelector:@selector(panel:updateWebAuthenticationPanel:)];
     m_delegateMethods.panelDismissWebAuthenticationPanelWithResult = [delegate respondsToSelector:@selector(panel:dismissWebAuthenticationPanelWithResult:)];
     m_delegateMethods.panelRequestPinWithRemainingRetriesCompletionHandler = [delegate respondsToSelector:@selector(panel:requestPINWithRemainingRetries:completionHandler:)];
+    m_delegateMethods.panelselectAssertionResponseCompletionHandler = [delegate respondsToSelector:@selector(panel:selectAssertionResponse:completionHandler:)];
 }
 
 RetainPtr<id <_WKWebAuthenticationPanelDelegate> > WebAuthenticationPanelClient::delegate()
@@ -62,6 +65,7 @@ static _WKWebAuthenticationPanelUpdate wkWebAuthenticationPanelUpdate(WebAuthent
 
 void WebAuthenticationPanelClient::updatePanel(WebAuthenticationStatus status) const
 {
+    // FIXME(206248)
     // Call delegates in the next run loop to prevent clients' reentrance that would potentially modify the state
     // of the current run loop in unexpected ways.
     RunLoop::main().dispatch([weakThis = makeWeakPtr(*this), this, status] {
@@ -93,6 +97,7 @@ static _WKWebAuthenticationResult wkWebAuthenticationResult(WebAuthenticationRes
 
 void WebAuthenticationPanelClient::dismissPanel(WebAuthenticationResult result) const
 {
+    // FIXME(206248)
     // Call delegates in the next run loop to prevent clients' reentrance that would potentially modify the state
     // of the current run loop in unexpected ways.
     RunLoop::main().dispatch([weakThis = makeWeakPtr(*this), this, result] {
@@ -112,6 +117,7 @@ void WebAuthenticationPanelClient::dismissPanel(WebAuthenticationResult result)
 
 void WebAuthenticationPanelClient::requestPin(uint64_t retries, CompletionHandler<void(const WTF::String&)>&& completionHandler) const
 {
+    // FIXME(206248)
     // Call delegates in the next run loop to prevent clients' reentrance that would potentially modify the state
     // of the current run loop in unexpected ways.
     RunLoop::main().dispatch([weakThis = makeWeakPtr(*this), this, retries, completionHandler = WTFMove(completionHandler)] () mutable {
@@ -141,6 +147,43 @@ void WebAuthenticationPanelClient::requestPin(uint64_t retries, CompletionHandle
     });
 }
 
+void WebAuthenticationPanelClient::selectAssertionResponse(const HashSet<Ref<WebCore::AuthenticatorAssertionResponse>>& responses, CompletionHandler<void(const WebCore::AuthenticatorAssertionResponse&)>&& completionHandler) const
+{
+    ASSERT(!responses.isEmpty());
+    auto nsResponses = adoptNS([[NSMutableArray alloc] initWithCapacity:responses.size()]);
+    for (auto& response : responses)
+        [nsResponses addObject:wrapper(API::WebAuthenticationAssertionResponse::create(response.copyRef()))];
+
+    // FIXME(206248)
+    // Call delegates in the next run loop to prevent clients' reentrance that would potentially modify the state
+    // of the current run loop in unexpected ways.
+    RunLoop::main().dispatch([weakThis = makeWeakPtr(*this), this, nsResponses = WTFMove(nsResponses), completionHandler = WTFMove(completionHandler)] () mutable {
+        if (!weakThis) {
+            completionHandler(static_cast<API::WebAuthenticationAssertionResponse&>([[nsResponses firstObject] _apiObject]).response());
+            return;
+        }
+
+        if (!m_delegateMethods.panelselectAssertionResponseCompletionHandler) {
+            completionHandler(static_cast<API::WebAuthenticationAssertionResponse&>([[nsResponses firstObject] _apiObject]).response());
+            return;
+        }
+
+        auto delegate = m_delegate.get();
+        if (!delegate) {
+            completionHandler(static_cast<API::WebAuthenticationAssertionResponse&>([[nsResponses firstObject] _apiObject]).response());
+            return;
+        }
+
+        auto checker = CompletionHandlerCallChecker::create(delegate.get(), @selector(panel:selectAssertionResponse:completionHandler:));
+        [delegate panel:m_panel selectAssertionResponse:nsResponses.get() completionHandler:makeBlockPtr([completionHandler = WTFMove(completionHandler), checker = WTFMove(checker)](_WKWebAuthenticationAssertionResponse *response) mutable {
+            if (checker->completionHandlerHasBeenCalled())
+                return;
+            checker->didCallCompletionHandler();
+            completionHandler(static_cast<API::WebAuthenticationAssertionResponse&>([response _apiObject]).response());
+        }).get()];
+    });
+}
+
 } // namespace WebKit
 
 #endif // ENABLE(WEB_AUTHN)
index 3c3f962..9a0a8db 100644 (file)
@@ -151,11 +151,11 @@ void CtapAuthenticator::continueGetNextAssertionAfterResponseReceived(Vector<uin
 
     if (!m_remainingAssertionResponses) {
         if (auto* observer = this->observer()) {
-            observer->selectAssertionResponses(m_assertionResponses, [this, weakThis = makeWeakPtr(*this)] (const Ref<AuthenticatorAssertionResponse>& response) {
+            observer->selectAssertionResponse(m_assertionResponses, [this, weakThis = makeWeakPtr(*this)] (const AuthenticatorAssertionResponse& response) {
                 ASSERT(RunLoop::isMain());
                 if (!weakThis)
                     return;
-                auto returnResponse = m_assertionResponses.take(response);
+                auto returnResponse = m_assertionResponses.take(const_cast<AuthenticatorAssertionResponse*>(&response));
                 if (!returnResponse)
                     return;
                 receiveRespond(WTFMove(*returnResponse));
index b19433a..370a9b5 100644 (file)
                577FF7822346E81C004EDFB9 /* APIWebAuthenticationPanelClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 577FF7812346E81C004EDFB9 /* APIWebAuthenticationPanelClient.h */; };
                577FF7852346ECAA004EDFB9 /* WebAuthenticationPanelClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 577FF7832346ECAA004EDFB9 /* WebAuthenticationPanelClient.h */; };
                578DC2982155A0020074E815 /* LocalAuthenticationSoftLink.h in Headers */ = {isa = PBXBuildFile; fileRef = 578DC2972155A0010074E815 /* LocalAuthenticationSoftLink.h */; };
+               579F1BF623C80DB600C7D4B4 /* _WKWebAuthenticationAssertionResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 579F1BF423C80DB600C7D4B4 /* _WKWebAuthenticationAssertionResponse.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               579F1BF923C80EC600C7D4B4 /* _WKWebAuthenticationAssertionResponseInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 579F1BF823C80EC600C7D4B4 /* _WKWebAuthenticationAssertionResponseInternal.h */; };
+               579F1BFC23C811CF00C7D4B4 /* APIWebAuthenticationAssertionResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 579F1BFA23C811CF00C7D4B4 /* APIWebAuthenticationAssertionResponse.h */; };
                57AC8F50217FEED90055438C /* HidConnection.h in Headers */ = {isa = PBXBuildFile; fileRef = 57AC8F4E217FEED90055438C /* HidConnection.h */; };
                57B4B46020B504AC00D4AD79 /* ClientCertificateAuthenticationXPCConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 57B4B45E20B504AB00D4AD79 /* ClientCertificateAuthenticationXPCConstants.h */; };
                57B826412304EB3E00B72EB0 /* NearFieldSPI.h in Headers */ = {isa = PBXBuildFile; fileRef = 57B826402304EB3E00B72EB0 /* NearFieldSPI.h */; };
                577FF7832346ECAA004EDFB9 /* WebAuthenticationPanelClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebAuthenticationPanelClient.h; sourceTree = "<group>"; };
                577FF7842346ECAA004EDFB9 /* WebAuthenticationPanelClient.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = WebAuthenticationPanelClient.mm; sourceTree = "<group>"; };
                578DC2972155A0010074E815 /* LocalAuthenticationSoftLink.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LocalAuthenticationSoftLink.h; sourceTree = "<group>"; };
+               579F1BF423C80DB600C7D4B4 /* _WKWebAuthenticationAssertionResponse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _WKWebAuthenticationAssertionResponse.h; sourceTree = "<group>"; };
+               579F1BF523C80DB600C7D4B4 /* _WKWebAuthenticationAssertionResponse.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKWebAuthenticationAssertionResponse.mm; sourceTree = "<group>"; };
+               579F1BF823C80EC600C7D4B4 /* _WKWebAuthenticationAssertionResponseInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _WKWebAuthenticationAssertionResponseInternal.h; sourceTree = "<group>"; };
+               579F1BFA23C811CF00C7D4B4 /* APIWebAuthenticationAssertionResponse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = APIWebAuthenticationAssertionResponse.h; sourceTree = "<group>"; };
+               579F1BFB23C811CF00C7D4B4 /* APIWebAuthenticationAssertionResponse.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = APIWebAuthenticationAssertionResponse.cpp; sourceTree = "<group>"; };
                57AC8F4E217FEED90055438C /* HidConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HidConnection.h; sourceTree = "<group>"; };
                57AC8F4F217FEED90055438C /* HidConnection.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = HidConnection.mm; sourceTree = "<group>"; };
                57B4B45D20B504AB00D4AD79 /* AuthenticationManagerCocoa.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AuthenticationManagerCocoa.mm; sourceTree = "<group>"; };
                                1A81B37F18BD66AD0007FDAC /* _WKVisitedLinkStore.h */,
                                1A81B37E18BD66AD0007FDAC /* _WKVisitedLinkStore.mm */,
                                1A81B38418BD673A0007FDAC /* _WKVisitedLinkStoreInternal.h */,
+                               579F1BF423C80DB600C7D4B4 /* _WKWebAuthenticationAssertionResponse.h */,
+                               579F1BF523C80DB600C7D4B4 /* _WKWebAuthenticationAssertionResponse.mm */,
+                               579F1BF823C80EC600C7D4B4 /* _WKWebAuthenticationAssertionResponseInternal.h */,
                                574728CF23456E98001700AF /* _WKWebAuthenticationPanel.h */,
                                574728D023456E98001700AF /* _WKWebAuthenticationPanel.mm */,
                                574728D3234570AE001700AF /* _WKWebAuthenticationPanelInternal.h */,
                                7C89D2921A67122F003A5FDE /* APIUserScript.h */,
                                2D8786211BDB58FF00D02ABB /* APIUserStyleSheet.cpp */,
                                2D8786221BDB58FF00D02ABB /* APIUserStyleSheet.h */,
+                               579F1BFB23C811CF00C7D4B4 /* APIWebAuthenticationAssertionResponse.cpp */,
+                               579F1BFA23C811CF00C7D4B4 /* APIWebAuthenticationAssertionResponse.h */,
                                57EBE269234676C5008D8AF9 /* APIWebAuthenticationPanel.cpp */,
                                57EBE268234676C5008D8AF9 /* APIWebAuthenticationPanel.h */,
                                577FF7812346E81C004EDFB9 /* APIWebAuthenticationPanelClient.h */,
                                2D8786201BDB57F500D02ABB /* _WKUserStyleSheetInternal.h in Headers */,
                                1A81B38118BD66AD0007FDAC /* _WKVisitedLinkStore.h in Headers */,
                                1A81B38518BD673A0007FDAC /* _WKVisitedLinkStoreInternal.h in Headers */,
+                               579F1BF623C80DB600C7D4B4 /* _WKWebAuthenticationAssertionResponse.h in Headers */,
+                               579F1BF923C80EC600C7D4B4 /* _WKWebAuthenticationAssertionResponseInternal.h in Headers */,
                                574728D123456E98001700AF /* _WKWebAuthenticationPanel.h in Headers */,
                                574728D4234570AE001700AF /* _WKWebAuthenticationPanelInternal.h in Headers */,
                                1AE286781C7E76510069AC4F /* _WKWebsiteDataSize.h in Headers */,
                                2D8786241BDB58FF00D02ABB /* APIUserStyleSheet.h in Headers */,
                                C5E1AFED16B21017006CC1F2 /* APIWebArchive.h in Headers */,
                                C5E1AFEF16B21029006CC1F2 /* APIWebArchiveResource.h in Headers */,
+                               579F1BFC23C811CF00C7D4B4 /* APIWebAuthenticationAssertionResponse.h in Headers */,
                                57EBE26A234676C5008D8AF9 /* APIWebAuthenticationPanel.h in Headers */,
                                577FF7822346E81C004EDFB9 /* APIWebAuthenticationPanelClient.h in Headers */,
                                1AE286841C7F93860069AC4F /* APIWebsiteDataRecord.h in Headers */,
index 768711b..d24e85e 100644 (file)
@@ -1,3 +1,18 @@
+2020-01-10  Jiewen Tan  <jiewen_tan@apple.com>
+
+        [WebAuthn] Implement SPI to tell UI clients to select assertion responses
+        https://bugs.webkit.org/show_bug.cgi?id=206112
+        <rdar://problem/58495733>
+
+        Reviewed by Alex Christensen.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/_WKWebAuthenticationPanel.mm:
+        (-[TestWebAuthenticationPanelDelegate panel:selectAssertionResponses:completionHandler:]):
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid-multiple-accounts.html: Added.
+        * TestWebKitAPI/Tests/WebKitCocoa/web-authentication-make-credential-hid.html:
+
 2020-01-14  Jonathan Bedard  <jbedard@apple.com>
 
         webkitpy: Pass ImageDiff commands in a single write
index a2d41e6..de31b1e 100644 (file)
                5797FE331EB15AB100B2F4A0 /* navigation-client-default-crypto.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 5797FE321EB15A8900B2F4A0 /* navigation-client-default-crypto.html */; };
                5798337E236019A4008E5547 /* web-authentication-make-credential-hid.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 5798337D2360196D008E5547 /* web-authentication-make-credential-hid.html */; };
                579833922368FA37008E5547 /* web-authentication-get-assertion-nfc-multiple-tags.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 5798337B235EB65C008E5547 /* web-authentication-get-assertion-nfc-multiple-tags.html */; };
+               579F1C0123C93AF500C7D4B4 /* web-authentication-get-assertion-hid-multiple-accounts.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 579F1BFF23C92FD300C7D4B4 /* web-authentication-get-assertion-hid-multiple-accounts.html */; };
                57A79857224AB34E00A7F6F1 /* WebCryptoMasterKey.mm in Sources */ = {isa = PBXBuildFile; fileRef = 57A79856224AB34E00A7F6F1 /* WebCryptoMasterKey.mm */; };
                57C3FA661F7C248F009D4B80 /* WeakPtr.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1CB9BC371A67482300FE5678 /* WeakPtr.cpp */; };
                57C6244E2346BCFA00383FE7 /* _WKWebAuthenticationPanel.mm in Sources */ = {isa = PBXBuildFile; fileRef = 57C6244D2346BCFA00383FE7 /* _WKWebAuthenticationPanel.mm */; };
                                2EBD9D0A2134730D002DA758 /* video.html in Copy Resources */,
                                CD577799211CE0E4001B371E /* web-audio-only.html in Copy Resources */,
                                57663DF32357E48900E85E09 /* web-authentication-get-assertion-hid-cancel.html in Copy Resources */,
+                               579F1C0123C93AF500C7D4B4 /* web-authentication-get-assertion-hid-multiple-accounts.html in Copy Resources */,
                                577454D02359B378008E1ED7 /* web-authentication-get-assertion-hid-no-credentials.html in Copy Resources */,
                                570D26FC23C3F87000D5CF67 /* web-authentication-get-assertion-hid-pin.html in Copy Resources */,
                                57663DEC234F1F9300E85E09 /* web-authentication-get-assertion-hid.html in Copy Resources */,
                5798337B235EB65C008E5547 /* web-authentication-get-assertion-nfc-multiple-tags.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "web-authentication-get-assertion-nfc-multiple-tags.html"; sourceTree = "<group>"; };
                5798337D2360196D008E5547 /* web-authentication-make-credential-hid.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "web-authentication-make-credential-hid.html"; sourceTree = "<group>"; };
                5798E2AF1CAF5C2800C5CBA0 /* ProvisionalURLNotChange.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ProvisionalURLNotChange.mm; sourceTree = "<group>"; };
+               579F1BFF23C92FD300C7D4B4 /* web-authentication-get-assertion-hid-multiple-accounts.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "web-authentication-get-assertion-hid-multiple-accounts.html"; sourceTree = "<group>"; };
                57A79856224AB34E00A7F6F1 /* WebCryptoMasterKey.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = WebCryptoMasterKey.mm; sourceTree = "<group>"; };
                57C6244D2346BCFA00383FE7 /* _WKWebAuthenticationPanel.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKWebAuthenticationPanel.mm; sourceTree = "<group>"; };
                57C6244F2346C1EC00383FE7 /* web-authentication-get-assertion.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "web-authentication-get-assertion.html"; sourceTree = "<group>"; };
                                CD57779B211CE6CE001B371E /* video-with-audio-and-web-audio.html */,
                                CD577798211CDE8F001B371E /* web-audio-only.html */,
                                57663DF22357E45D00E85E09 /* web-authentication-get-assertion-hid-cancel.html */,
+                               579F1BFF23C92FD300C7D4B4 /* web-authentication-get-assertion-hid-multiple-accounts.html */,
                                577454CF2359B338008E1ED7 /* web-authentication-get-assertion-hid-no-credentials.html */,
                                570D26FB23C3F86500D5CF67 /* web-authentication-get-assertion-hid-pin.html */,
                                57663DEB234F1F8000E85E09 /* web-authentication-get-assertion-hid.html */,
index 0cb2478..a975833 100644 (file)
 #import <WebKit/WKPreferencesPrivate.h>
 #import <WebKit/WKUIDelegatePrivate.h>
 #import <WebKit/_WKExperimentalFeature.h>
+#import <WebKit/_WKWebAuthenticationAssertionResponse.h>
 #import <WebKit/_WKWebAuthenticationPanel.h>
 #import <wtf/BlockPtr.h>
+#import <wtf/RandomNumber.h>
 #import <wtf/text/StringConcatenateNumbers.h>
 
 static bool webAuthenticationPanelRan = false;
@@ -46,6 +48,7 @@ static bool webAuthenticationPanelUpdateMultipleNFCTagsPresent = false;
 static bool webAuthenticationPanelUpdateNoCredentialsFound = false;
 static bool webAuthenticationPanelCancelImmediately = false;
 static String webAuthenticationPanelPin;
+static BOOL webAuthenticationPanelNullUserHandle = NO;
 
 @interface TestWebAuthenticationPanelDelegate : NSObject <_WKWebAuthenticationPanelDelegate>
 @end
@@ -91,6 +94,20 @@ static String webAuthenticationPanelPin;
     completionHandler(webAuthenticationPanelPin);
 }
 
+- (void)panel:(_WKWebAuthenticationPanel *)panel selectAssertionResponse:(NSArray < _WKWebAuthenticationAssertionResponse *> *)responses completionHandler:(void (^)(_WKWebAuthenticationAssertionResponse *))completionHandler
+{
+    EXPECT_EQ(responses.count, 2ul);
+    for (_WKWebAuthenticationAssertionResponse *response in responses) {
+        EXPECT_TRUE([response.name isEqual:@"johnpsmith@example.com"] || [response.name isEqual:@""]);
+        EXPECT_TRUE([response.displayName isEqual:@"John P. Smith"] || [response.displayName isEqual:@""]);
+        EXPECT_TRUE([[response.userHandle base64EncodedStringWithOptions:0] isEqual:@"MIIBkzCCATigAwIBAjCCAZMwggE4oAMCAQIwggGTMII="] || !response.userHandle);
+    }
+
+    auto index = weakRandomUint32() % 2;
+    webAuthenticationPanelNullUserHandle = responses[index].userHandle ? NO : YES;
+    completionHandler(responses[index]);
+}
+
 @end
 
 @interface TestWebAuthenticationPanelFakeDelegate : NSObject <_WKWebAuthenticationPanelDelegate>
@@ -872,6 +889,40 @@ TEST(WebAuthenticationPanel, GetAssertionPin)
     [webView waitForMessage:@"Succeeded!"];
 }
 
+TEST(WebAuthenticationPanel, MultipleAccountsNullDelegate)
+{
+    reset();
+    RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-hid-multiple-accounts" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
+
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
+
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
+    auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
+    [delegate setIsNull:true];
+    [webView setUIDelegate:delegate.get()];
+
+    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+    [webView waitForMessage:@"Succeeded!"];
+}
+
+TEST(WebAuthenticationPanel, MultipleAccounts)
+{
+    reset();
+    RetainPtr<NSURL> testURL = [[NSBundle mainBundle] URLForResource:@"web-authentication-get-assertion-hid-multiple-accounts" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"];
+
+    auto *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES];
+    [[configuration preferences] _setEnabled:YES forExperimentalFeature:webAuthenticationExperimentalFeature()];
+
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSZeroRect configuration:configuration]);
+    auto delegate = adoptNS([[TestWebAuthenticationPanelUIDelegate alloc] init]);
+    [webView setUIDelegate:delegate.get()];
+
+    [webView loadRequest:[NSURLRequest requestWithURL:testURL.get()]];
+    [webView waitForMessage:@"Succeeded!"];
+    EXPECT_EQ([[webView stringByEvaluatingJavaScript:@"userHandle"] isEqualToString:@"<null>"], webAuthenticationPanelNullUserHandle);
+}
+
 } // namespace TestWebKitAPI
 
 #endif // ENABLE(WEB_AUTHN)
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid-multiple-accounts.html b/Tools/TestWebKitAPI/Tests/WebKitCocoa/web-authentication-get-assertion-hid-multiple-accounts.html
new file mode 100644 (file)
index 0000000..d637e42
--- /dev/null
@@ -0,0 +1,56 @@
+<input type="text" id="input">
+<script>
+    const testAssertionMessageLongBase64 =
+        "AKUBomJpZFhAKAitzuj+Tslzelf3/vZwIGtDQNgoKeFd5oEieYzhyzA65saf0tK2" +
+        "w/mooa7tQtGgDdwZIjOhjcuZ0pQ1ajoE4GR0eXBlanB1YmxpYy1rZXkCWCVGzH+5" +
+        "Z51VstuQkuHI2eXh0Ct1gPC0gSx3CWLh5I9a2AEAAABQA1hHMEUCIQCSFTuuBWgB" +
+        "4/F0VB7DlUVM09IHPmxe1MzHUwRoCRZbCAIgGKov6xoAx2MEf6/6qNs8OutzhP2C" +
+        "QoJ1L7Fe64G9uBcEpGJpZFggMIIBkzCCATigAwIBAjCCAZMwggE4oAMCAQIwggGT" +
+        "MIJkaWNvbngoaHR0cHM6Ly9waWNzLmFjbWUuY29tLzAwL3AvYUJqampwcVBiLnBu" +
+        "Z2RuYW1ldmpvaG5wc21pdGhAZXhhbXBsZS5jb21rZGlzcGxheU5hbWVtSm9obiBQ" +
+        "LiBTbWl0aAUC";
+    const testAssertionMessageBase64 =
+        "AKMBomJpZFhAKAitzuj+Tslzelf3/vZwIGtDQNgoKeFd5oEieYzhyzA65saf0tK2" +
+        "w/mooa7tQtGgDdwZIjOhjcuZ0pQ1ajoE4GR0eXBlanB1YmxpYy1rZXkCWCVGzH+5" +
+        "Z51VstuQkuHI2eXh0Ct1gPC0gSx3CWLh5I9a2AEAAABQA1hHMEUCIQCSFTuuBWgB" +
+        "4/F0VB7DlUVM09IHPmxe1MzHUwRoCRZbCAIgGKov6xoAx2MEf6/6qNs8OutzhP2C" +
+        "QoJ1L7Fe64G9uBc=";
+    if (window.internals) {
+        internals.setMockWebAuthenticationConfiguration({ silentFailure: true, hid: { payloadBase64: [testAssertionMessageLongBase64, testAssertionMessageBase64] } });
+        internals.withUserGesture(() => { input.focus(); });
+    }
+
+    function bytesToHexString(bytes)
+    {
+        if (!bytes)
+            return null;
+
+        bytes = new Uint8Array(bytes);
+        var hexBytes = [];
+
+        for (var i = 0; i < bytes.length; ++i) {
+            var byteString = bytes[i].toString(16);
+            if (byteString.length < 2)
+                byteString = "0" + byteString;
+            hexBytes.push(byteString);
+        }
+
+        return hexBytes.join("");
+    }
+
+    const options = {
+        publicKey: {
+            challenge: new Uint8Array(16),
+            timeout: 1000
+        }
+    };
+
+    navigator.credentials.get(options).then(credential => {
+        userHandle = bytesToHexString(credential.response.userHandle);
+        // console.log("Succeeded!");
+        window.webkit.messageHandlers.testHandler.postMessage("Succeeded!");
+    }, error => {
+        // console.log(error.message);
+        window.webkit.messageHandlers.testHandler.postMessage(error.message);
+    });
+</script>
index 5b6aebe..e981a70 100644 (file)
@@ -39,7 +39,7 @@
             },
             challenge: new Uint8Array(16),
             pubKeyCredParams: [{ type: "public-key", alg: -7 }],
-            timeout: 100
+            timeout: 100,
         }
     };