Unreviewed, rolling out r228444.
[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 #if ENABLE(WEB_AUTHN)
31
32 #include "AbortSignal.h"
33 #include "CredentialCreationOptions.h"
34 #include "CredentialRequestOptions.h"
35 #include "Document.h"
36 #include "ExceptionOr.h"
37 #include "JSBasicCredential.h"
38 #include "PublicKeyCredential.h"
39 #include "SecurityOrigin.h"
40 #include <wtf/MainThread.h>
41
42 namespace WebCore {
43
44 CredentialsContainer::PendingPromise::PendingPromise(Ref<DeferredPromise>&& promise, std::unique_ptr<Timer>&& timer)
45     : promise(WTFMove(promise))
46     , timer(WTFMove(timer))
47 {
48 }
49
50 CredentialsContainer::PendingPromise::PendingPromise(Ref<DeferredPromise>&& promise)
51     : promise(WTFMove(promise))
52 {
53 }
54
55 CredentialsContainer::CredentialsContainer(WeakPtr<Document>&& document)
56     : m_document(WTFMove(document))
57     , m_workQueue(WorkQueue::create("com.apple.WebKit.CredentialQueue"))
58 {
59 }
60
61 // The following implements https://w3c.github.io/webappsec-credential-management/#same-origin-with-its-ancestors
62 // as of 14 November 2017.
63 bool CredentialsContainer::doesHaveSameOriginAsItsAncestors()
64 {
65     if (!m_document)
66         return false;
67
68     auto& origin = m_document->securityOrigin();
69     for (auto* document = m_document->parentDocument(); document; document = document->parentDocument()) {
70         if (!originsMatch(document->securityOrigin(), origin))
71             return false;
72     }
73     return true;
74 }
75
76 // FIXME(181946): Since the underlying authenticator model is not clear at this moment, the timer is moved to CredentialsContainer such that
77 // timer can stay with main thread and therefore can easily time out activities on the work queue.
78 // 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
79 // since it doesn't support observers (or other means) to trigger the actual abort action. Enhancement to AbortSignal is needed.
80 template<typename OperationType>
81 void CredentialsContainer::dispatchTask(OperationType&& operation, Ref<DeferredPromise>&& promise, std::optional<unsigned long> timeOutInMs)
82 {
83     ASSERT(isMainThread());
84     if (!m_document)
85         return;
86
87     auto* promiseIndex = promise.ptr();
88     auto weakThis = m_weakPtrFactory.createWeakPtr(*this);
89     // FIXME(181947): We should probably trim timeOutInMs to some max allowable number.
90     if (timeOutInMs) {
91         auto pendingPromise = PendingPromise::create(WTFMove(promise), std::make_unique<Timer>([promiseIndex, weakThis] () {
92             ASSERT(isMainThread());
93             if (weakThis) {
94                 // A lock should not be needed as all callbacks are executed in the main thread.
95                 if (auto promise = weakThis->m_pendingPromises.take(promiseIndex))
96                     promise.value()->promise->reject(Exception { NotAllowedError });
97             }
98         }));
99         pendingPromise->timer->startOneShot(Seconds(timeOutInMs.value() / 1000.0));
100         m_pendingPromises.add(promiseIndex, WTFMove(pendingPromise));
101     } else
102         m_pendingPromises.add(promiseIndex, PendingPromise::create(WTFMove(promise)));
103
104     auto task = [promiseIndex, weakThis, origin = m_document->securityOrigin().isolatedCopy(), isSameOriginWithItsAncestors = doesHaveSameOriginAsItsAncestors(), operation = WTFMove(operation)] () {
105         auto result = operation(origin, isSameOriginWithItsAncestors);
106         callOnMainThread([promiseIndex, weakThis, result = WTFMove(result)] () mutable {
107             if (weakThis) {
108                 // A lock should not be needed as all callbacks are executed in the main thread.
109                 if (auto promise = weakThis->m_pendingPromises.take(promiseIndex)) {
110                     if (result.hasException())
111                         promise.value()->promise->reject(result.releaseException());
112                     else
113                         promise.value()->promise->resolve<IDLNullable<IDLInterface<BasicCredential>>>(result.returnValue().get());
114                 }
115             }
116         });
117     };
118     m_workQueue->dispatch(WTFMove(task));
119 }
120
121 void CredentialsContainer::get(CredentialRequestOptions&& options, Ref<DeferredPromise>&& promise)
122 {
123     // The following implements https://www.w3.org/TR/credential-management-1/#algorithm-request as of 4 August 2017
124     // with enhancement from 14 November 2017 Editor's Draft.
125     // FIXME: Optional options are passed with no contents. It should be std::optional.
126     if ((!options.signal && !options.publicKey) || !m_document) {
127         promise->reject(Exception { NotSupportedError });
128         return;
129     }
130     if (options.signal && options.signal->aborted()) {
131         promise->reject(Exception { AbortError });
132         return;
133     }
134     // Step 1-2.
135     ASSERT(m_document->isSecureContext());
136
137     // Step 3 is enhanced with doesHaveSameOriginAsItsAncestors.
138     // Step 4-6. Shortcut as we only support PublicKeyCredential which can only
139     // be requested from [[discoverFromExternalSource]].
140     if (!options.publicKey) {
141         promise->reject(Exception { NotSupportedError });
142         return;
143     }
144
145     auto timeout = options.publicKey->timeout;
146     auto operation = [options = WTFMove(options.publicKey.value())] (const SecurityOrigin& origin, bool isSameOriginWithItsAncestors) {
147         return PublicKeyCredential::discoverFromExternalSource(origin, options, isSameOriginWithItsAncestors);
148     };
149     dispatchTask(WTFMove(operation), WTFMove(promise), timeout);
150 }
151
152 void CredentialsContainer::store(const BasicCredential&, Ref<DeferredPromise>&& promise)
153 {
154     promise->reject(Exception { NotSupportedError });
155 }
156
157 void CredentialsContainer::isCreate(CredentialCreationOptions&& options, Ref<DeferredPromise>&& promise)
158 {
159     // The following implements https://www.w3.org/TR/credential-management-1/#algorithm-create as of 4 August 2017
160     // with enhancement from 14 November 2017 Editor's Draft.
161     // FIXME: Optional options are passed with no contents. It should be std::optional.
162     if ((!options.signal && !options.publicKey) || !m_document) {
163         promise->reject(Exception { NotSupportedError });
164         return;
165     }
166     if (options.signal && options.signal->aborted()) {
167         promise->reject(Exception { AbortError });
168         return;
169     }
170     // Step 1-2.
171     ASSERT(m_document->isSecureContext());
172
173     // Step 3-4. Shortcut as we only support one kind of credentials.
174     if (!options.publicKey) {
175         promise->reject(Exception { NotSupportedError });
176         return;
177     }
178
179     auto timeout = options.publicKey->timeout;
180     // Step 5-7.
181     auto operation = [options = WTFMove(options.publicKey.value())] (const SecurityOrigin& origin, bool isSameOriginWithItsAncestors) {
182         // Shortcut as well.
183         return PublicKeyCredential::create(origin, options, isSameOriginWithItsAncestors);
184     };
185     dispatchTask(WTFMove(operation), WTFMove(promise), timeout);
186 }
187
188 void CredentialsContainer::preventSilentAccess(Ref<DeferredPromise>&& promise) const
189 {
190     promise->reject(Exception { NotSupportedError });
191 }
192
193 } // namespace WebCore
194
195 #endif // ENABLE(WEB_AUTHN)