Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 19033e95 authored by Arpan's avatar Arpan
Browse files

Communicate Get Flow Biometric Success to Provider

This sets up the biometric flow to communicate the results to the
provider for the get flow.

This is to finalize the biometric API call so that the end
result of the authentication can be communicated to the provider,
completed the E2E Get Happy Path. This
specifies the success case, which is the pivotal case to complete the
flow. Other cases, such as failures, or other callback values
(help/confirm/etc..) are presently logged and will be productionized as
soon as possible.

However, this should have the success flow checked in
with ways to identify failure cases from the non triggerable flows
presently in the framework.

To make this full E2E, this also ensures that the initial input is
completely allowed as expected by Jetpack, though everything is being
tested. Therefore, it is protected from being reachable by flags.

The Create version will follow up with the create changes, and the
required exhaustive when statement has been left with a 'TODO' for the
create case; that case will be tested to not break any E2E existing
flows.

Bug: 327620327
Test: Visual, Build, and Unit in Progress: see b/329874867
Change-Id: Icf0d8459d784880a2d307c4997103451ca5b29fb
parent 58e33aa4
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@
    <uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR"/>
    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
    <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/>
    <uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_ADVANCED"/>
    <uses-permission android:name="android.permission.ACCESS_INSTANT_APPS" />
    <uses-permission android:name="android.permission.USE_BIOMETRIC" />

+34 −6
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.get.ProviderInfo
import com.android.credentialmanager.model.get.RemoteEntryInfo
import com.android.credentialmanager.TAG
import com.android.credentialmanager.model.BiometricRequestInfo
import com.android.credentialmanager.model.EntryInfo

fun EntryInfo.getIntentSenderRequest(
@@ -139,7 +140,7 @@ private fun getCredentialOptionInfoList(
                    isDefaultIconPreferredAsSingleProvider =
                            credentialEntry.isDefaultIconPreferredAsSingleProvider,
                    affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
                    isSupportingSingleTap = false, // TODO(b/326243754) : Fill in as product built
                    biometricRequest = predetermineAndValidateBiometricFlow(it),
                )
                )
            }
@@ -168,7 +169,7 @@ private fun getCredentialOptionInfoList(
                    isDefaultIconPreferredAsSingleProvider =
                            credentialEntry.isDefaultIconPreferredAsSingleProvider,
                    affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
                    isSupportingSingleTap = false, // TODO(b/326243754) : Fill in as product built
                    biometricRequest = predetermineAndValidateBiometricFlow(it),
                )
                )
            }
@@ -196,9 +197,7 @@ private fun getCredentialOptionInfoList(
                    isDefaultIconPreferredAsSingleProvider =
                            credentialEntry.isDefaultIconPreferredAsSingleProvider,
                    affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
                    isSupportingSingleTap = false, // TODO(b/326243754) : Fill in as product built
                    // TODO(b/326243754) : If required info above is missing, force condition to
                    // false.
                    biometricRequest = predetermineAndValidateBiometricFlow(it),
                )
                )
            }
@@ -210,6 +209,36 @@ private fun getCredentialOptionInfoList(
    }
    return result
}

/**
 * This validates if this is a biometric flow or not, and if it is, this returns the expected
 * [BiometricRequestInfo]. Namely, the biometric flow must have at least the
 * ALLOWED_AUTHENTICATORS bit passed from Jetpack.
 * Note that the required values, such as the provider info's icon or display name, or the entries
 * credential type or userName, and finally the display info's app name, are non-null and must
 * exist to run through the flow.
 * // TODO(b/326243754) : Presently, due to dependencies, the opId bit is parsed but is never
 * // expected to be used. When it is added, it should be lightly validated.
 */
private fun predetermineAndValidateBiometricFlow(
    it: Entry
): BiometricRequestInfo? {
    // TODO(b/326243754) : When available, use the official jetpack structured type
    val allowedAuthenticators: Int? = it.slice.items.firstOrNull {
        it.hasHint("androidx.credentials." +
                "provider.credentialEntry.SLICE_HINT_ALLOWED_AUTHENTICATORS")
    }?.int

    // This is optional and does not affect validating the biometric flow in any case
    val opId: Int? = it.slice.items.firstOrNull {
        it.hasHint("androidx.credentials.provider.credentialEntry.SLICE_HINT_CRYPTO_OP_ID")
    }?.int
    if (allowedAuthenticators != null) {
        return BiometricRequestInfo(opId = opId, allowedAuthenticators = allowedAuthenticators)
    }
    return null
}

val Slice.credentialEntry: CredentialEntry?
    get() =
        try {
@@ -226,7 +255,6 @@ val Slice.credentialEntry: CredentialEntry?
            CustomCredentialEntry.fromSlice(this)
        }


/**
 * Note: caller required handle empty list due to parsing error.
 */
+28 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.credentialmanager.model

/**
 * This allows reading the data from the request, and holding that state around the framework.
 * The [opId] bit is required for some authentication flows where CryptoObjects are used.
 * The [allowedAuthenticators] is needed for all flows, and our flow ensures this value is never
 * null.
 */
data class BiometricRequestInfo(
    val opId: Int? = null,
    val allowedAuthenticators: Int
)
 No newline at end of file
+2 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.credentialmanager.model.creation
import android.app.PendingIntent
import android.content.Intent
import android.graphics.drawable.Drawable
import com.android.credentialmanager.model.BiometricRequestInfo
import com.android.credentialmanager.model.EntryInfo
import java.time.Instant

@@ -36,6 +37,7 @@ class CreateOptionInfo(
    val lastUsedTime: Instant,
    val footerDescription: String?,
    val allowAutoSelect: Boolean,
    val biometricRequest: BiometricRequestInfo? = null,
) : EntryInfo(
    providerId,
    entryKey,
+2 −1
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.credentialmanager.model.get
import android.app.PendingIntent
import android.content.Intent
import android.graphics.drawable.Drawable
import com.android.credentialmanager.model.BiometricRequestInfo
import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.EntryInfo
import java.time.Instant
@@ -49,7 +50,7 @@ class CredentialEntryInfo(
                              // "For <value-of-entryGroupId>" on the more-option screen.
    val isDefaultIconPreferredAsSingleProvider: Boolean,
    val affiliatedDomain: String?,
    val isSupportingSingleTap: Boolean,
    val biometricRequest: BiometricRequestInfo? = null,
) : EntryInfo(
    providerId,
    entryKey,
Loading