chromium/components/webauthn/android/java/src/org/chromium/components/webauthn/WebauthnBrowserBridge.java

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.webauthn;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.NativeMethods;

import org.chromium.base.Callback;
import org.chromium.content_public.browser.RenderFrameHost;

import java.util.List;

/**
 * Provides a bridge from the the Android Web Authentication request handlers to the embedding
 * browser.
 */
@JNINamespace("webauthn")
public class WebauthnBrowserBridge {
    /** Owner of the bridge should implement this interface and cache the bridge. */
    public interface Provider {
        WebauthnBrowserBridge getBridge();
    }

    private long mNativeWebauthnBrowserBridge;

    /**
     * Provides a list of discoverable credentials for user selection. If this is a conditional UI
     * request, then these credentials become available as options for autofill UI on sign-in input
     * fields. For non-conditional requests, a selection sheet is shown immediately. The callback is
     * invoked when a user selects one of the credentials from the list.
     *
     * @param frameHost The RenderFrameHost for the frame that generated the request.
     * @param credentialList The list of credentials that can be used as autofill suggestions.
     * @param isConditionalRequest Boolean indicating whether this is a conditional UI request or
     *     not.
     * @param getAssertionCallback The callback to be invoked with the credential ID of a selected
     *     credential.
     * @param hybridCallback The callback to be invoked if a user initiates a cross-device hybrid
     *     sign-in.
     */
    public void onCredentialsDetailsListReceived(
            RenderFrameHost frameHost,
            List<WebauthnCredentialDetails> credentialList,
            boolean isConditionalRequest,
            Callback<byte[]> getAssertionCallback,
            Runnable hybridCallback) {
        assert credentialList != null;
        assert getAssertionCallback != null;
        prepareNativeBrowserBridgeIfRequired();

        WebauthnCredentialDetails[] credentialArray =
                credentialList.toArray(new WebauthnCredentialDetails[credentialList.size()]);
        WebauthnBrowserBridgeJni.get()
                .onCredentialsDetailsListReceived(
                        mNativeWebauthnBrowserBridge,
                        WebauthnBrowserBridge.this,
                        credentialArray,
                        frameHost,
                        isConditionalRequest,
                        getAssertionCallback,
                        hybridCallback);
    }

    /**
     * Provides the C++ side |hasResults| and |fullAssertion| to be consumed during user interaction
     * (i.e. focusing login forms).
     *
     * @param frameHost The RenderFrameHost for the frame that generated the request.
     * @param hasResults The response from credMan whether there are credentials for the
     *     GetAssertion request.
     * @param fullAssertion The CredMan request to trigger UI for credential selection for the
     *     completed conditional request.
     */
    public void onCredManConditionalRequestPending(
            RenderFrameHost frameHost, boolean hasResults, Callback<Boolean> fullAssertion) {
        prepareNativeBrowserBridgeIfRequired();

        WebauthnBrowserBridgeJni.get()
                .onCredManConditionalRequestPending(
                        mNativeWebauthnBrowserBridge, frameHost, hasResults, fullAssertion);
    }

    /**
     * Notifies the C++ side that the credMan UI is closed.
     *
     * @param frameHost The RenderFrameHost related to this CredMan call
     * @param success true iff user is authenticated
     */
    public void onCredManUiClosed(RenderFrameHost frameHost, boolean success) {
        prepareNativeBrowserBridgeIfRequired();

        WebauthnBrowserBridgeJni.get()
                .onCredManUiClosed(mNativeWebauthnBrowserBridge, frameHost, success);
    }

    public void onPasswordCredentialReceived(
            RenderFrameHost frameHost, String username, String password) {
        prepareNativeBrowserBridgeIfRequired();

        WebauthnBrowserBridgeJni.get()
                .onPasswordCredentialReceived(
                        mNativeWebauthnBrowserBridge, frameHost, username, password);
    }

    /**
     * Notifies the native code that an outstanding Conditional UI request initiated through
     * onCredentialsDetailsListReceived has been completed or canceled.
     *
     * @param frameHost The RenderFrameHost for the frame that generated the cancellation.
     */
    public void cleanupRequest(RenderFrameHost frameHost) {
        // This should never be called without a bridge already having been created.
        assert mNativeWebauthnBrowserBridge != 0;

        WebauthnBrowserBridgeJni.get().cleanupRequest(mNativeWebauthnBrowserBridge, frameHost);
    }

    /**
     * Notifies the native code that an outstanding Android Credential Management
     * prepareGetCredential request has been canceled.
     *
     * @param frameHost The RenderFrameHost for the frame that generated the cancellation.
     */
    public void cleanupCredManRequest(RenderFrameHost frameHost) {
        // This should never be called without a bridge already having been created.
        assert mNativeWebauthnBrowserBridge != 0;

        WebauthnBrowserBridgeJni.get()
                .cleanupCredManRequest(mNativeWebauthnBrowserBridge, frameHost);
    }

    public void destroy() {
        if (mNativeWebauthnBrowserBridge == 0) return;
        WebauthnBrowserBridgeJni.get().destroy(mNativeWebauthnBrowserBridge);
        mNativeWebauthnBrowserBridge = 0;
    }

    @CalledByNative
    private static String getWebauthnCredentialDetailsUserName(WebauthnCredentialDetails cred) {
        return cred.mUserName;
    }

    @CalledByNative
    private static String getWebauthnCredentialDetailsUserDisplayName(
            WebauthnCredentialDetails cred) {
        return cred.mUserDisplayName;
    }

    @CalledByNative
    private static byte[] getWebauthnCredentialDetailsUserId(WebauthnCredentialDetails cred) {
        return cred.mUserId;
    }

    @CalledByNative
    private static byte[] getWebauthnCredentialDetailsCredentialId(WebauthnCredentialDetails cred) {
        return cred.mCredentialId;
    }

    private void prepareNativeBrowserBridgeIfRequired() {
        if (mNativeWebauthnBrowserBridge == 0) {
            mNativeWebauthnBrowserBridge =
                    WebauthnBrowserBridgeJni.get()
                            .createNativeWebauthnBrowserBridge(WebauthnBrowserBridge.this);
        }
    }

    @NativeMethods
    interface Natives {
        // Native methods are implemented in webauthn_browser_bridge.cc.
        long createNativeWebauthnBrowserBridge(WebauthnBrowserBridge caller);

        void onCredentialsDetailsListReceived(
                long nativeWebauthnBrowserBridge,
                WebauthnBrowserBridge caller,
                WebauthnCredentialDetails[] credentialList,
                RenderFrameHost frameHost,
                boolean isConditionalRequest,
                Callback<byte[]> getAssertionCallback,
                Runnable hybridCallback);

        void onCredManConditionalRequestPending(
                long nativeWebauthnBrowserBridge,
                RenderFrameHost frameHost,
                boolean hasResults,
                Callback<Boolean> fullAssertion);

        void onCredManUiClosed(
                long nativeWebauthnBrowserBridge, RenderFrameHost frameHost, boolean success);

        void onPasswordCredentialReceived(
                long nativeWebauthnBrowserBridge,
                RenderFrameHost frameHost,
                String username,
                String password);

        void cleanupRequest(long nativeWebauthnBrowserBridge, RenderFrameHost frameHost);

        void cleanupCredManRequest(long nativeWebauthnBrowserBridge, RenderFrameHost frameHost);

        void destroy(long nativeWebauthnBrowserBridge);
    }
}