AuthenticatorManager.cpp [plain text]
#include "config.h"
#include "AuthenticatorManager.h"
#if ENABLE(WEB_AUTHN)
#include <WebCore/AuthenticatorTransport.h>
#include <WebCore/PublicKeyCredentialCreationOptions.h>
#include <wtf/MonotonicTime.h>
namespace WebKit {
using namespace WebCore;
namespace AuthenticatorManagerInternal {
#if PLATFORM(MAC)
const size_t maxTransportNumber = 2;
#else
const size_t maxTransportNumber = 1;
#endif
const unsigned maxTimeOutValue = 120000;
static AuthenticatorManager::TransportSet collectTransports(const Optional<PublicKeyCredentialCreationOptions::AuthenticatorSelectionCriteria>& authenticatorSelection)
{
AuthenticatorManager::TransportSet result;
if (!authenticatorSelection || !authenticatorSelection->authenticatorAttachment) {
auto addResult = result.add(AuthenticatorTransport::Internal);
ASSERT_UNUSED(addResult, addResult.isNewEntry);
#if PLATFORM(MAC)
addResult = result.add(AuthenticatorTransport::Usb);
ASSERT_UNUSED(addResult, addResult.isNewEntry);
#endif
return result;
}
if (authenticatorSelection->authenticatorAttachment == PublicKeyCredentialCreationOptions::AuthenticatorAttachment::Platform) {
auto addResult = result.add(AuthenticatorTransport::Internal);
ASSERT_UNUSED(addResult, addResult.isNewEntry);
return result;
}
if (authenticatorSelection->authenticatorAttachment == PublicKeyCredentialCreationOptions::AuthenticatorAttachment::CrossPlatform) {
#if PLATFORM(MAC)
auto addResult = result.add(AuthenticatorTransport::Usb);
ASSERT_UNUSED(addResult, addResult.isNewEntry);
#endif
return result;
}
ASSERT_NOT_REACHED();
return result;
}
static AuthenticatorManager::TransportSet collectTransports(const Vector<PublicKeyCredentialDescriptor>& allowCredentials)
{
AuthenticatorManager::TransportSet result;
if (allowCredentials.isEmpty()) {
auto addResult = result.add(AuthenticatorTransport::Internal);
ASSERT_UNUSED(addResult, addResult.isNewEntry);
#if PLATFORM(MAC)
addResult = result.add(AuthenticatorTransport::Usb);
ASSERT_UNUSED(addResult, addResult.isNewEntry);
#endif
return result;
}
for (auto& allowCredential : allowCredentials) {
if (allowCredential.transports.isEmpty()) {
result.add(AuthenticatorTransport::Internal);
#if PLATFORM(MAC)
result.add(AuthenticatorTransport::Usb);
return result;
#endif
}
if (!result.contains(AuthenticatorTransport::Internal) && allowCredential.transports.contains(AuthenticatorTransport::Internal))
result.add(AuthenticatorTransport::Internal);
#if PLATFORM(MAC)
if (!result.contains(AuthenticatorTransport::Usb) && allowCredential.transports.contains(AuthenticatorTransport::Usb))
result.add(AuthenticatorTransport::Usb);
#endif
if (result.size() >= maxTransportNumber)
return result;
}
ASSERT(result.size() < maxTransportNumber);
return result;
}
}
AuthenticatorManager::AuthenticatorManager()
: m_requestTimeOutTimer(RunLoop::main(), this, &AuthenticatorManager::timeOutTimerFired)
{
}
void AuthenticatorManager::makeCredential(const Vector<uint8_t>& hash, const PublicKeyCredentialCreationOptions& options, Callback&& callback)
{
using namespace AuthenticatorManagerInternal;
if (m_pendingCompletionHandler) {
callback(ExceptionData { NotAllowedError, "A request is pending."_s });
return;
}
m_pendingRequestData = { hash, true, options, { } };
m_pendingCompletionHandler = WTFMove(callback);
initTimeOutTimer(options.timeout);
startDiscovery(collectTransports(options.authenticatorSelection));
}
void AuthenticatorManager::getAssertion(const Vector<uint8_t>& hash, const PublicKeyCredentialRequestOptions& options, Callback&& callback)
{
using namespace AuthenticatorManagerInternal;
if (m_pendingCompletionHandler) {
callback(ExceptionData { NotAllowedError, "A request is pending."_s });
return;
}
m_pendingRequestData = { hash, false, { }, options };
m_pendingCompletionHandler = WTFMove(callback);
initTimeOutTimer(options.timeout);
ASSERT(m_services.isEmpty());
startDiscovery(collectTransports(options.allowCredentials));
}
void AuthenticatorManager::clearStateAsync()
{
RunLoop::main().dispatch([weakThis = makeWeakPtr(*this)] {
if (!weakThis)
return;
weakThis->m_pendingRequestData = { };
ASSERT(!weakThis->m_pendingCompletionHandler);
weakThis->m_services.clear();
weakThis->m_authenticators.clear();
});
}
void AuthenticatorManager::authenticatorAdded(Ref<Authenticator>&& authenticator)
{
ASSERT(RunLoop::isMain());
authenticator->setObserver(*this);
authenticator->handleRequest(m_pendingRequestData);
auto addResult = m_authenticators.add(WTFMove(authenticator));
ASSERT_UNUSED(addResult, addResult.isNewEntry);
}
void AuthenticatorManager::respondReceived(Respond&& respond)
{
ASSERT(RunLoop::isMain());
if (!m_requestTimeOutTimer.isActive())
return;
ASSERT(m_pendingCompletionHandler);
if (WTF::holds_alternative<PublicKeyCredentialData>(respond)) {
m_pendingCompletionHandler(WTFMove(respond));
clearStateAsync();
m_requestTimeOutTimer.stop();
return;
}
respondReceivedInternal(WTFMove(respond));
}
UniqueRef<AuthenticatorTransportService> AuthenticatorManager::createService(WebCore::AuthenticatorTransport transport, AuthenticatorTransportService::Observer& observer) const
{
return AuthenticatorTransportService::create(transport, observer);
}
void AuthenticatorManager::respondReceivedInternal(Respond&&)
{
}
void AuthenticatorManager::startDiscovery(const TransportSet& transports)
{
using namespace AuthenticatorManagerInternal;
ASSERT(m_services.isEmpty() && transports.size() <= maxTransportNumber);
for (auto& transport : transports) {
auto service = createService(transport, *this);
service->startDiscovery();
m_services.append(WTFMove(service));
}
}
void AuthenticatorManager::initTimeOutTimer(const Optional<unsigned>& timeOutInMs)
{
using namespace AuthenticatorManagerInternal;
unsigned timeOutInMsValue = std::min(maxTimeOutValue, timeOutInMs.valueOr(maxTimeOutValue));
m_requestTimeOutTimer.startOneShot(Seconds::fromMilliseconds(timeOutInMsValue));
}
void AuthenticatorManager::timeOutTimerFired()
{
ASSERT(m_requestTimeOutTimer.isActive());
m_pendingCompletionHandler((ExceptionData { NotAllowedError, "Operation timed out."_s }));
clearStateAsync();
}
}
#endif // ENABLE(WEB_AUTHN)