AuthenticatorPresenterCoordinator.mm [plain text]
/*
* 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 "AuthenticatorPresenterCoordinator.h"
#if ENABLE(WEB_AUTHN)
#import "AuthenticatorManager.h"
#import "WKASCAuthorizationPresenterDelegate.h"
#import <wtf/BlockPtr.h>
#import "AuthenticationServicesCoreSoftLink.h"
namespace WebKit {
using namespace WebCore;
AuthenticatorPresenterCoordinator::AuthenticatorPresenterCoordinator(const AuthenticatorManager& manager, const String& rpId, const TransportSet& transports, ClientDataType type)
: m_manager(makeWeakPtr(manager))
{
#if HAVE(ASC_AUTH_UI)
m_context = adoptNS([allocASCAuthorizationPresentationContextInstance() initWithRequestContext:nullptr appIdentifier:nullptr]);
if ([getASCAuthorizationPresentationContextClass() instancesRespondToSelector:@selector(setServiceName:)])
[m_context setServiceName:rpId];
switch (type) {
case ClientDataType::Create:
if (transports.contains(AuthenticatorTransport::Internal))
[m_context addLoginChoice:adoptNS([allocASCPlatformPublicKeyCredentialLoginChoiceInstance() initRegistrationChoice]).get()];
if (transports.contains(AuthenticatorTransport::Usb) || transports.contains(AuthenticatorTransport::Nfc))
[m_context addLoginChoice:adoptNS([allocASCSecurityKeyPublicKeyCredentialLoginChoiceInstance() initRegistrationChoice]).get()];
break;
case ClientDataType::Get:
if ((transports.contains(AuthenticatorTransport::Usb) || transports.contains(AuthenticatorTransport::Nfc)) && !transports.contains(AuthenticatorTransport::Internal))
[m_context addLoginChoice:adoptNS([allocASCSecurityKeyPublicKeyCredentialLoginChoiceInstance() initAssertionPlaceholderChoice]).get()];
break;
default:
ASSERT_NOT_REACHED();
}
m_presenterDelegate = [[WKASCAuthorizationPresenterDelegate alloc] initWithCoordinator:*this];
m_presenter = [allocASCAuthorizationPresenterInstance() init];
[m_presenter setDelegate:m_presenterDelegate.get()];
auto completionHandler = makeBlockPtr([manager = m_manager] (id<ASCCredentialProtocol> credential, NSError *error) mutable {
if (credential || !error)
return;
LOG_ERROR("Couldn't complete the authenticator presentation context: %@", error);
// This block can be executed in another thread.
RunLoop::main().dispatch([manager] () mutable {
if (manager)
manager->cancel();
});
});
if (type == ClientDataType::Get && transports.contains(AuthenticatorTransport::Internal)) {
m_delayedPresentationNeedsSecurityKey = (transports.contains(AuthenticatorTransport::Usb) || transports.contains(AuthenticatorTransport::Nfc));
m_delayedPresentation = [completionHandler = WTFMove(completionHandler), this] {
[m_presenter presentAuthorizationWithContext:m_context.get() completionHandler:completionHandler.get()];
};
return;
}
[m_presenter presentAuthorizationWithContext:m_context.get() completionHandler:completionHandler.get()];
#endif // HAVE(ASC_AUTH_UI)
}
AuthenticatorPresenterCoordinator::~AuthenticatorPresenterCoordinator()
{
if (m_laContextHandler)
m_laContextHandler(nullptr);
if (m_responseHandler)
m_responseHandler(nullptr);
if (m_pinHandler)
m_pinHandler(String());
}
void AuthenticatorPresenterCoordinator::updatePresenter(WebAuthenticationStatus status)
{
#if HAVE(ASC_AUTH_UI)
switch (status) {
case WebAuthenticationStatus::PinBlocked: {
auto error = adoptNS([[NSError alloc] initWithDomain:ASCAuthorizationErrorDomain code:ASCAuthorizationErrorAuthenticatorPermanentlyLocked userInfo:nil]);
m_credentialRequestHandler(nil, error.get());
break;
}
case WebAuthenticationStatus::PinAuthBlocked: {
auto error = adoptNS([[NSError alloc] initWithDomain:ASCAuthorizationErrorDomain code:ASCAuthorizationErrorAuthenticatorTemporarilyLocked userInfo:nil]);
m_credentialRequestHandler(nil, error.get());
break;
}
case WebAuthenticationStatus::PinInvalid: {
auto error = adoptNS([[NSError alloc] initWithDomain:ASCAuthorizationErrorDomain code:ASCAuthorizationErrorPINInvalid userInfo:nil]);
m_credentialRequestHandler(nil, error.get());
break;
}
case WebAuthenticationStatus::MultipleNFCTagsPresent: {
auto error = adoptNS([[NSError alloc] initWithDomain:ASCAuthorizationErrorDomain code:ASCAuthorizationErrorMultipleNFCTagsPresent userInfo:nil]);
[m_presenter updateInterfaceForUserVisibleError:error.get()];
break;
}
case WebAuthenticationStatus::LANoCredential: {
if (m_delayedPresentationNeedsSecurityKey) {
[m_context addLoginChoice:adoptNS([allocASCSecurityKeyPublicKeyCredentialLoginChoiceInstance() initAssertionPlaceholderChoice]).get()];
m_delayedPresentation();
break;
}
auto error = adoptNS([[NSError alloc] initWithDomain:ASCAuthorizationErrorDomain code:ASCAuthorizationErrorNoCredentialsFound userInfo:nil]);
[m_presenter presentError:error.get() forService:[m_context serviceName] completionHandler:makeBlockPtr([manager = m_manager] {
RunLoop::main().dispatch([manager] () mutable {
if (manager)
manager->cancel();
});
}).get()];
break;
}
case WebAuthenticationStatus::NoCredentialsFound: {
auto error = adoptNS([[NSError alloc] initWithDomain:ASCAuthorizationErrorDomain code:ASCAuthorizationErrorNoCredentialsFound userInfo:nil]);
[m_presenter updateInterfaceForUserVisibleError:error.get()];
break;
}
case WebAuthenticationStatus::LAError: {
auto error = adoptNS([[NSError alloc] initWithDomain:ASCAuthorizationErrorDomain code:ASCAuthorizationErrorLAError userInfo:nil]);
[m_presenter updateInterfaceForUserVisibleError:error.get()];
break;
}
case WebAuthenticationStatus::LAExcludeCredentialsMatched: {
auto error = adoptNS([[NSError alloc] initWithDomain:ASCAuthorizationErrorDomain code:ASCAuthorizationErrorLAExcludeCredentialsMatched userInfo:nil]);
[m_presenter updateInterfaceForUserVisibleError:error.get()];
break;
}
default:
ASSERT_NOT_REACHED();
break;
}
#endif // HAVE(ASC_AUTH_UI)
}
void AuthenticatorPresenterCoordinator::requestPin(uint64_t, CompletionHandler<void(const String&)>&& completionHandler)
{
#if HAVE(ASC_AUTH_UI)
if (m_pinHandler)
m_pinHandler(String());
m_pinHandler = WTFMove(completionHandler);
if (m_presentedPIN)
return;
m_presentedPIN = true;
[m_presenter presentPINEntryInterface];
#endif // HAVE(ASC_AUTH_UI)
}
void AuthenticatorPresenterCoordinator::selectAssertionResponse(Vector<Ref<AuthenticatorAssertionResponse>>&& responses, WebAuthenticationSource source, CompletionHandler<void(AuthenticatorAssertionResponse*)>&& completionHandler)
{
#if HAVE(ASC_AUTH_UI)
if (m_responseHandler)
m_responseHandler(nullptr);
m_responseHandler = WTFMove(completionHandler);
if (source == WebAuthenticationSource::External) {
auto loginChoices = adoptNS([[NSMutableArray alloc] init]);
m_credentials.clear();
for (auto& response : responses) {
if (!response->name())
continue;
RetainPtr<NSData> userHandle;
if (response->userHandle())
userHandle = adoptNS([[NSData alloc] initWithBytes:response->userHandle()->data() length:response->userHandle()->byteLength()]);
auto loginChoice = adoptNS([allocASCSecurityKeyPublicKeyCredentialLoginChoiceInstance() initWithName:response->name() displayName:response->displayName() userHandle:userHandle.get()]);
[loginChoices addObject:loginChoice.get()];
m_credentials.add(response->name(), WTFMove(response));
}
[m_presenter updateInterfaceWithLoginChoices:loginChoices.get()];
return;
}
if (source == WebAuthenticationSource::Local) {
m_credentials.clear();
for (auto& response : responses) {
RetainPtr<NSData> userHandle;
if (response->userHandle())
userHandle = adoptNS([[NSData alloc] initWithBytes:response->userHandle()->data() length:response->userHandle()->byteLength()]);
auto loginChoice = adoptNS([allocASCPlatformPublicKeyCredentialLoginChoiceInstance() initWithName:response->name() displayName:response->displayName() userHandle:userHandle.get()]);
[m_context addLoginChoice:loginChoice.get()];
m_credentials.add(response->name(), WTFMove(response));
}
if (m_delayedPresentationNeedsSecurityKey)
[m_context addLoginChoice:adoptNS([allocASCSecurityKeyPublicKeyCredentialLoginChoiceInstance() initAssertionPlaceholderChoice]).get()];
m_delayedPresentation();
return;
}
#endif // HAVE(ASC_AUTH_UI)
}
void AuthenticatorPresenterCoordinator::requestLAContextForUserVerification(CompletionHandler<void(LAContext *)>&& completionHandler)
{
if (m_laContext) {
completionHandler(m_laContext.get());
return;
}
m_laContextHandler = WTFMove(completionHandler);
}
void AuthenticatorPresenterCoordinator::dimissPresenter(WebAuthenticationResult result)
{
#if HAVE(ASC_AUTH_UI)
if (result == WebAuthenticationResult::Succeeded && m_credentialRequestHandler) {
// FIXME(219767): Replace the ASCAppleIDCredential with the upcoming WebAuthn credentials one.
// This is just a place holder to tell the UI that the ceremony succeeds.
m_credentialRequestHandler(adoptNS([WebKit::allocASCAppleIDCredentialInstance() initWithUser:@"" identityToken:adoptNS([[NSData alloc] init]).get()]).get(), nil);
return;
}
[m_presenter dismissWithError:nil];
#endif // HAVE(ASC_AUTH_UI)
}
void AuthenticatorPresenterCoordinator::setLAContext(LAContext *context)
{
if (m_laContextHandler) {
m_laContextHandler(context);
return;
}
m_laContext = context;
}
void AuthenticatorPresenterCoordinator::didSelectAssertionResponse(const String& credentialName, LAContext *context)
{
auto response = m_credentials.take(credentialName);
if (!response)
return;
if (context)
response->setLAContext(context);
m_responseHandler(response.get());
}
void AuthenticatorPresenterCoordinator::setPin(const String& pin)
{
m_pinHandler(pin);
}
} // namespace WebKit
#endif // ENABLE(WEB_AUTHN)