[WebAuthN] Implement PublicKeyCredential’s [[DiscoverFromExternalSource]] with a...
[WebKit-https.git] / Source / WebCore / Modules / credentialmanagement / CredentialsContainer.cpp
1 /*
2  * Copyright (C) 2017 Google Inc. All rights reserved.
3  * Copyright (C) 2017 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24  * THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "CredentialsContainer.h"
29
30 #include "AbortSignal.h"
31 #include "CredentialCreationOptions.h"
32 #include "CredentialRequestOptions.h"
33 #include "Document.h"
34 #include "ExceptionOr.h"
35 #include "JSBasicCredential.h"
36 #include "PublicKeyCredential.h"
37 #include "SecurityOrigin.h"
38 #include <wtf/MainThread.h>
39
40 namespace WebCore {
41
42 CredentialsContainer::PendingPromise::PendingPromise(Ref<DeferredPromise>&& promise, std::unique_ptr<Timer>&& timer)
43     : promise(WTFMove(promise))
44     , timer(WTFMove(timer))
45 {
46 }
47
48 CredentialsContainer::PendingPromise::PendingPromise(Ref<DeferredPromise>&& promise)
49     : promise(WTFMove(promise))
50 {
51 }
52
53 CredentialsContainer::CredentialsContainer(WeakPtr<Document>&& document)
54     : m_document(WTFMove(document))
55     , m_workQueue(WorkQueue::create("com.apple.WebKit.CredentialQueue"))
56 {
57 }
58
59 // The following implements https://w3c.github.io/webappsec-credential-management/#same-origin-with-its-ancestors
60 // as of 14 November 2017.
61 bool CredentialsContainer::doesHaveSameOriginAsItsAncestors()
62 {
63     if (!m_document)
64         return false;
65
66     auto& origin = m_document->securityOrigin();
67     for (auto* document = m_document->parentDocument(); document; document = document->parentDocument()) {
68         if (!originsMatch(document->securityOrigin(), origin))
69             return false;
70     }
71     return true;
72 }
73
74 // FIXME(181946): Since the underlying authenticator model is not clear at this moment, the timer is moved to CredentialsContainer such that
75 // timer can stay with main thread and therefore can easily time out activities on the work queue.
76 // FIXME(181945): The usages of AbortSignal are also moved here for the very same reason. Also the AbortSignal is kind of bogus at this moment
77 // since it doesn't support observers (or other means) to trigger the actual abort action. Enhancement to AbortSignal is needed.
78 template<typename OperationType>
79 void CredentialsContainer::dispatchTask(OperationType&& operation, Ref<DeferredPromise>&& promise, std::optional<unsigned long> timeOutInMs)
80 {
81     ASSERT(isMainThread());
82     if (!m_document)
83         return;
84
85     auto* promiseIndex = promise.ptr();
86     auto weakThis = m_weakPtrFactory.createWeakPtr(*this);
87     // FIXME(181947): We should probably trim timeOutInMs to some max allowable number.
88     if (timeOutInMs) {
89         auto pendingPromise = PendingPromise::create(WTFMove(promise), std::make_unique<Timer>([promiseIndex, weakThis] () {
90             ASSERT(isMainThread());
91             if (weakThis) {
92                 // A lock should not be needed as all callbacks are executed in the main thread.
93                 if (auto promise = weakThis->m_pendingPromises.take(promiseIndex))
94                     promise.value()->promise->reject(Exception { NotAllowedError });
95             }
96         }));
97         pendingPromise->timer->startOneShot(Seconds(timeOutInMs.value() / 1000.0));
98         m_pendingPromises.add(promiseIndex, WTFMove(pendingPromise));
99     } else
100         m_pendingPromises.add(promiseIndex, PendingPromise::create(WTFMove(promise)));
101
102     auto task = [promiseIndex, weakThis, origin = m_document->securityOrigin().isolatedCopy(), isSameOriginWithItsAncestors = doesHaveSameOriginAsItsAncestors(), operation = WTFMove(operation)] () {
103         auto result = operation(origin, isSameOriginWithItsAncestors);
104         callOnMainThread([promiseIndex, weakThis, result = WTFMove(result)] () mutable {
105             if (weakThis) {
106                 // A lock should not be needed as all callbacks are executed in the main thread.
107                 if (auto promise = weakThis->m_pendingPromises.take(promiseIndex)) {
108                     if (result.hasException())
109                         promise.value()->promise->reject(result.releaseException());
110                     else
111                         promise.value()->promise->resolve<IDLNullable<IDLInterface<BasicCredential>>>(result.returnValue().get());
112                 }
113             }
114         });
115     };
116     m_workQueue->dispatch(WTFMove(task));
117 }
118
119 void CredentialsContainer::get(CredentialRequestOptions&& options, Ref<DeferredPromise>&& promise)
120 {
121     // The following implements https://www.w3.org/TR/credential-management-1/#algorithm-request as of 4 August 2017
122     // with enhancement from 14 November 2017 Editor's Draft.
123     // FIXME: Optional options are passed with no contents. It should be std::optional.
124     if ((!options.signal && !options.publicKey) || !m_document) {
125         promise->reject(Exception { NotSupportedError });
126         return;
127     }
128     if (options.signal && options.signal->aborted()) {
129         promise->reject(Exception { AbortError });
130         return;
131     }
132     // Step 1-2.
133     ASSERT(m_document->isSecureContext());
134
135     // Step 3 is enhanced with doesHaveSameOriginAsItsAncestors.
136     // Step 4-6. Shortcut as we only support PublicKeyCredential which can only
137     // be requested from [[discoverFromExternalSource]].
138     if (!options.publicKey) {
139         promise->reject(Exception { NotSupportedError });
140         return;
141     }
142
143     auto timeout = options.publicKey->timeout;
144     auto operation = [options = WTFMove(options.publicKey.value())] (const SecurityOrigin& origin, bool isSameOriginWithItsAncestors) {
145         return PublicKeyCredential::discoverFromExternalSource(origin, options, isSameOriginWithItsAncestors);
146     };
147     dispatchTask(WTFMove(operation), WTFMove(promise), timeout);
148 }
149
150 void CredentialsContainer::store(const BasicCredential&, Ref<DeferredPromise>&& promise)
151 {
152     promise->reject(Exception { NotSupportedError });
153 }
154
155 void CredentialsContainer::isCreate(CredentialCreationOptions&& options, Ref<DeferredPromise>&& promise)
156 {
157     // The following implements https://www.w3.org/TR/credential-management-1/#algorithm-create as of 4 August 2017
158     // with enhancement from 14 November 2017 Editor's Draft.
159     // FIXME: Optional options are passed with no contents. It should be std::optional.
160     if ((!options.signal && !options.publicKey) || !m_document) {
161         promise->reject(Exception { NotSupportedError });
162         return;
163     }
164     if (options.signal && options.signal->aborted()) {
165         promise->reject(Exception { AbortError });
166         return;
167     }
168     // Step 1-2.
169     ASSERT(m_document->isSecureContext());
170
171     // Step 3-4. Shortcut as we only support one kind of credentials.
172     if (!options.publicKey) {
173         promise->reject(Exception { NotSupportedError });
174         return;
175     }
176
177     auto timeout = options.publicKey->timeout;
178     // Step 5-7.
179     auto operation = [options = WTFMove(options.publicKey.value())] (const SecurityOrigin& origin, bool isSameOriginWithItsAncestors) {
180         // Shortcut as well.
181         return PublicKeyCredential::create(origin, options, isSameOriginWithItsAncestors);
182     };
183     dispatchTask(WTFMove(operation), WTFMove(promise), timeout);
184 }
185
186 void CredentialsContainer::preventSilentAccess(Ref<DeferredPromise>&& promise) const
187 {
188     promise->reject(Exception { NotSupportedError });
189 }
190
191 } // namespace WebCore