3fad75ad0d3d9632ffb178b07a389c18f9823f24
[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: 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 // https://bugs.webkit.org/show_bug.cgi?id=181946.
77 // FIXME: The usages of AbortSignal are also moved here for the very same reason. Also the AbortSignal is kind of bogus at this moment
78 // since it doesn't support observers (or other means) to trigger the actual abort action. Enhancement to AbortSignal is needed.
79 // https://bugs.webkit.org/show_bug.cgi?id=181945.
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: We should probably trim timeOutInMs to some max allowable number.
90     // https://bugs.webkit.org/show_bug.cgi?id=181947
91     if (timeOutInMs) {
92         auto pendingPromise = PendingPromise::create(WTFMove(promise), std::make_unique<Timer>([promiseIndex, weakThis] () {
93             ASSERT(isMainThread());
94             if (weakThis) {
95                 // A lock should not be needed as all callbacks are executed in the main thread.
96                 if (auto promise = weakThis->m_pendingPromises.take(promiseIndex))
97                     promise.value()->promise->reject(Exception { NotAllowedError });
98             }
99         }));
100         pendingPromise->timer->startOneShot(Seconds(timeOutInMs.value() / 1000.0));
101         m_pendingPromises.add(promiseIndex, WTFMove(pendingPromise));
102     } else
103         m_pendingPromises.add(promiseIndex, PendingPromise::create(WTFMove(promise)));
104
105     auto task = [promiseIndex, weakThis, origin = m_document->securityOrigin().isolatedCopy(), isSameOriginWithItsAncestors = doesHaveSameOriginAsItsAncestors(), operation = WTFMove(operation)] () {
106         auto result = operation(origin, isSameOriginWithItsAncestors);
107         callOnMainThread([promiseIndex, weakThis, result = WTFMove(result)] () mutable {
108             if (weakThis) {
109                 // A lock should not be needed as all callbacks are executed in the main thread.
110                 if (auto promise = weakThis->m_pendingPromises.take(promiseIndex)) {
111                     if (result.hasException())
112                         promise.value()->promise->reject(result.releaseException());
113                     else {
114                         // FIXME: Got some crazy compile error when I was trying to pass RHS to the resolve method.
115                         RefPtr<BasicCredential> credential = result.releaseReturnValue();
116                         promise.value()->promise->resolve<IDLNullable<IDLInterface<BasicCredential>>>(credential.get());
117                     }
118                 }
119             }
120         });
121     };
122     m_workQueue->dispatch(WTFMove(task));
123 }
124
125 void CredentialsContainer::get(CredentialRequestOptions&& options, Ref<DeferredPromise>&& promise)
126 {
127     // FIXME: Optional options are passed with no contents. It should be std::optional.
128     if ((!options.signal && !options.publicKey) || !m_document) {
129         promise->reject(Exception { NotSupportedError });
130         return;
131     }
132     if (options.signal && options.signal->aborted()) {
133         promise->reject(Exception { AbortError });
134         return;
135     }
136     ASSERT(m_document->isSecureContext());
137
138     // The followings is a shortcut to https://www.w3.org/TR/credential-management-1/#algorithm-request,
139     // as we only support PublicKeyCredential which can only be requested from [[discoverFromExternalSource]].
140     if (!options.publicKey) {
141         promise->reject(Exception { NotSupportedError });
142         return;
143     }
144
145     auto operation = [options = WTFMove(options)] (const SecurityOrigin& origin, bool isSameOriginWithItsAncestors) {
146         return PublicKeyCredential::discoverFromExternalSource(origin, options, isSameOriginWithItsAncestors);
147     };
148     dispatchTask(WTFMove(operation), WTFMove(promise), options.publicKey->timeout);
149 }
150
151 void CredentialsContainer::store(const BasicCredential&, Ref<DeferredPromise>&& promise)
152 {
153     promise->reject(Exception { NotSupportedError });
154 }
155
156 void CredentialsContainer::isCreate(CredentialCreationOptions&& options, Ref<DeferredPromise>&& promise)
157 {
158     // The following implements https://www.w3.org/TR/credential-management-1/#algorithm-create as of 4 August 2017
159     // with enhancement from 14 November 2017 Editor's Draft.
160     // FIXME: Optional options are passed with no contents. It should be std::optional.
161     if ((!options.signal && !options.publicKey) || !m_document) {
162         promise->reject(Exception { NotSupportedError });
163         return;
164     }
165     if (options.signal && options.signal->aborted()) {
166         promise->reject(Exception { AbortError });
167         return;
168     }
169     // Step 1-2.
170     ASSERT(m_document->isSecureContext());
171
172     // Step 3-4. Shortcut as we only support one kind of credentials.
173     if (!options.publicKey) {
174         promise->reject(Exception { NotSupportedError });
175         return;
176     }
177
178     auto timeout = options.publicKey->timeout;
179     // Step 5-7.
180     auto operation = [options = WTFMove(options.publicKey.value())] (const SecurityOrigin& origin, bool isSameOriginWithItsAncestors) {
181         // Shortcut as well.
182         return PublicKeyCredential::create(origin, options, isSameOriginWithItsAncestors);
183     };
184     dispatchTask(WTFMove(operation), WTFMove(promise), options.publicKey->timeout);
185 }
186
187 void CredentialsContainer::preventSilentAccess(Ref<DeferredPromise>&& promise) const
188 {
189     promise->reject(Exception { NotSupportedError });
190 }
191
192 } // namespace WebCore