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

Commit 4c0db23c authored by Helen Qin's avatar Helen Qin
Browse files

Remove test data fallback from the UI app.

Also discovered a bug around parsing the cancel request, see the one
line change in CredentialSelectorActivity.kt.

Test: local e2e verification
Bug: 265041783
Change-Id: I25f01613be1a636736ce738b067f9467ab2d6cc7
parent 2e03f9f5
Loading
Loading
Loading
Loading
+29 −268
Original line number Diff line number Diff line
@@ -18,14 +18,8 @@ package com.android.credentialmanager

import android.content.Context
import android.content.Intent
import android.credentials.CreateCredentialRequest
import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
import android.credentials.CredentialOption
import android.credentials.GetCredentialRequest
import android.credentials.ui.AuthenticationEntry
import android.credentials.ui.CancelUiRequest
import android.credentials.ui.Constants
import android.credentials.ui.Entry
import android.credentials.ui.CreateCredentialProviderData
import android.credentials.ui.GetCredentialProviderData
import android.credentials.ui.DisabledProviderData
@@ -35,23 +29,16 @@ import android.credentials.ui.BaseDialogResult
import android.credentials.ui.ProviderPendingIntentResponse
import android.credentials.ui.UserSelectionDialogResult
import android.os.IBinder
import android.os.Binder
import android.os.Bundle
import android.os.ResultReceiver
import android.util.Log
import com.android.credentialmanager.createflow.DisabledProviderInfo
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.findAutoSelectEntry
import androidx.credentials.CreateCredentialRequest.DisplayInfo
import androidx.credentials.CreatePublicKeyCredentialRequest
import androidx.credentials.CreatePasswordRequest
import androidx.credentials.GetPasswordOption
import androidx.credentials.GetPublicKeyCredentialOption
import com.android.credentialmanager.common.ProviderActivityState

import java.time.Instant

/**
 * Client for interacting with Credential Manager. Also holds data inputs from it.
 *
@@ -63,7 +50,7 @@ class CredentialManagerRepo(
    intent: Intent,
    userConfigRepo: UserConfigRepo,
) {
    val requestInfo: RequestInfo
    val requestInfo: RequestInfo?
    private val providerEnabledList: List<ProviderData>
    private val providerDisabledList: List<DisabledProviderData>?
    // TODO: require non-null.
@@ -75,27 +62,30 @@ class CredentialManagerRepo(
        requestInfo = intent.extras?.getParcelable(
            RequestInfo.EXTRA_REQUEST_INFO,
            RequestInfo::class.java
        ) ?: testGetRequestInfo()
        )

        val originName: String? = when (requestInfo.type) {
            RequestInfo.TYPE_CREATE -> requestInfo.createCredentialRequest?.origin
            RequestInfo.TYPE_GET -> requestInfo.getCredentialRequest?.origin
        val originName: String? = when (requestInfo?.type) {
            RequestInfo.TYPE_CREATE -> requestInfo?.createCredentialRequest?.origin
            RequestInfo.TYPE_GET -> requestInfo?.getCredentialRequest?.origin
            else -> null
        }

        providerEnabledList = when (requestInfo.type) {
        providerEnabledList = when (requestInfo?.type) {
            RequestInfo.TYPE_CREATE ->
                intent.extras?.getParcelableArrayList(
                    ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
                    CreateCredentialProviderData::class.java
                ) ?: testCreateCredentialEnabledProviderList()
                ) ?: emptyList()
            RequestInfo.TYPE_GET ->
                intent.extras?.getParcelableArrayList(
                    ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
                    GetCredentialProviderData::class.java
                ) ?: testGetCredentialProviderList()
                ) ?: emptyList()
            else -> {
                throw IllegalStateException("Unrecognized request type: ${requestInfo.type}")
                Log.d(
                    com.android.credentialmanager.common.Constants.LOG_TAG,
                    "Unrecognized request type: ${requestInfo?.type}")
                emptyList()
            }
        }

@@ -103,7 +93,7 @@ class CredentialManagerRepo(
            intent.extras?.getParcelableArrayList(
                ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST,
                DisabledProviderData::class.java
            ) ?: testDisabledProviderList()
            )

        resultReceiver = intent.getParcelableExtra(
            Constants.EXTRA_RESULT_RECEIVER,
@@ -115,7 +105,7 @@ class CredentialManagerRepo(
            CancelUiRequestState(getAppLabel(context.getPackageManager(), it.appPackageName))
        }

        initialUiState = when (requestInfo.type) {
        initialUiState = when (requestInfo?.type) {
            RequestInfo.TYPE_CREATE -> {
                val defaultProviderId = userConfigRepo.getDefaultProviderId()
                val isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
@@ -151,7 +141,17 @@ class CredentialManagerRepo(
                    cancelRequestState = cancelUiRequestState
                )
            }
            else -> throw IllegalStateException("Unrecognized request type: ${requestInfo.type}")
            else -> {
                if (cancellationRequest != null) {
                    UiState(
                        createCredentialUiState = null,
                        getCredentialUiState = null,
                        cancelRequestState = cancelUiRequestState,
                    )
                } else {
                    throw IllegalStateException("Unrecognized request type: ${requestInfo?.type}")
                }
            }
        }
    }

@@ -174,7 +174,7 @@ class CredentialManagerRepo(
    }

    fun onCancel(cancelCode: Int) {
        sendCancellationCode(cancelCode, requestInfo.token, resultReceiver)
        sendCancellationCode(cancelCode, requestInfo?.token, resultReceiver)
    }

    fun onOptionSelected(
@@ -185,7 +185,7 @@ class CredentialManagerRepo(
        resultData: Intent? = null,
    ) {
        val userSelectionDialogResult = UserSelectionDialogResult(
            requestInfo.token,
            requestInfo?.token,
            providerId,
            entryKey,
            entrySubkey,
@@ -213,10 +213,9 @@ class CredentialManagerRepo(

    // IMPORTANT: new invocation should be mindful that this method can throw.
    private fun getCreateProviderEnableListInitialUiState(): List<EnabledProviderInfo> {
        val providerEnabledList = CreateFlowUtils.toEnabledProviderList(
        return CreateFlowUtils.toEnabledProviderList(
            providerEnabledList as List<CreateCredentialProviderData>, context
        )
        return providerEnabledList
    }

    private fun getCreateProviderDisableListInitialUiState(): List<DisabledProviderInfo> {
@@ -253,242 +252,4 @@ class CredentialManagerRepo(
            )
        }
    }

    // TODO: below are prototype functionalities. To be removed for productionization.
    private fun testCreateCredentialEnabledProviderList(): List<CreateCredentialProviderData> {
        return listOf(
            CreateCredentialProviderData
                .Builder("io.enpass.app")
                .setSaveEntries(
                    listOf<Entry>(
                        CreateTestUtils.newCreateEntry(
                            context,
                            "key1", "subkey-1", "elisa.beckett@gmail.com",
                            20, 7, 27, Instant.ofEpochSecond(10L),
                            "You can use your passkey on this or other devices. It is saved to " +
                                "the Password Manager for elisa.beckett@gmail.com."
                        ),
                        CreateTestUtils.newCreateEntry(
                            context,
                            "key1", "subkey-2", "elisa.work@google.com",
                            20, 7, 27, Instant.ofEpochSecond(12L),
                            null
                        ),
                    )
                ).setRemoteEntry(
                    CreateTestUtils.newRemoteCreateEntry(context, "key2", "subkey-1")
                ).build(),
            CreateCredentialProviderData
                .Builder("com.dashlane")
                .setSaveEntries(
                    listOf<Entry>(
                        CreateTestUtils.newCreateEntry(
                            context,
                            "key1", "subkey-3", "elisa.beckett@dashlane.com",
                            20, 7, 27, Instant.ofEpochSecond(11L),
                            null
                        ),
                        CreateTestUtils.newCreateEntry(
                            context,
                            "key1", "subkey-4", "elisa.work@dashlane.com",
                            20, 7, 27, Instant.ofEpochSecond(14L),
                            "You can use your passkey on this or other devices. It is saved to " +
                                "the Password Manager for elisa.work@dashlane.com"
                        ),
                    )
                ).build(),
        )
    }

    private fun testDisabledProviderList(): List<DisabledProviderData>? {
        return listOf(
            DisabledProviderData("com.lastpass.lpandroid"),
            DisabledProviderData("com.google.android.youtube")
        )
    }

    private fun testGetCredentialProviderList(): List<GetCredentialProviderData> {
        return listOf(
            GetCredentialProviderData.Builder("io.enpass.app")
                .setCredentialEntries(
                    listOf(
                        GetTestUtils.newPasswordEntry(
                            context, "key1", "subkey-1", "elisa.family@outlook.com", null,
                            Instant.ofEpochSecond(8000L)
                        ),
                        GetTestUtils.newPasskeyEntry(
                            context, "key1", "subkey-1", "elisa.bakery@gmail.com", "Elisa Beckett",
                            null
                        ),
                        GetTestUtils.newPasswordEntry(
                            context, "key1", "subkey-2", "elisa.bakery@gmail.com", null,
                            Instant.ofEpochSecond(10000L)
                        ),
                        GetTestUtils.newPasskeyEntry(
                            context, "key1", "subkey-3", "elisa.family@outlook.com",
                            "Elisa Beckett", Instant.ofEpochSecond(500L)
                        ),
                    )
                ).setAuthenticationEntries(
                    listOf(
                        GetTestUtils.newAuthenticationEntry(
                            context, "key2", "subkey-1", "locked-user1@gmail.com",
                            AuthenticationEntry.STATUS_LOCKED
                        ),
                        GetTestUtils.newAuthenticationEntry(
                            context, "key2", "subkey-2", "locked-user2@gmail.com",
                            AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT
                        ),
                    )
                ).setActionChips(
                    listOf(
                        GetTestUtils.newActionEntry(
                            context, "key3", "subkey-1",
                            "Open Google Password Manager", "elisa.beckett@gmail.com"
                        ),
                        GetTestUtils.newActionEntry(
                            context, "key3", "subkey-2",
                            "Open Google Password Manager", "beckett-family@gmail.com"
                        ),
                    )
                ).setRemoteEntry(
                    GetTestUtils.newRemoteCredentialEntry(context, "key4", "subkey-1")
                ).build(),
            GetCredentialProviderData.Builder("com.dashlane")
                .setCredentialEntries(
                    listOf<Entry>(
                        GetTestUtils.newPasswordEntry(
                            context, "key1", "subkey-2", "elisa.family@outlook.com", null,
                            Instant.ofEpochSecond(9000L)
                        ),
                        GetTestUtils.newPasswordEntry(
                            context, "key1", "subkey-3", "elisa.work@outlook.com", null,
                            Instant.ofEpochSecond(11000L)
                        ),
                    )
                ).setAuthenticationEntries(
                     listOf(
                         GetTestUtils.newAuthenticationEntry(
                             context, "key2", "subkey-1", "foo@email.com",
                             AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT,
                         )
                     )
                ).setActionChips(
                    listOf(
                        GetTestUtils.newActionEntry(
                            context, "key3", "subkey-1", "Open Enpass",
                            "Manage passwords"
                        ),
                    )
                ).build(),
        )
    }

    private fun testCreatePasskeyRequestInfo(): RequestInfo {
        val request = CreatePublicKeyCredentialRequest(
            "{\"extensions\": {\n" +
                "                     \"webauthn.loc\": true\n" +
                "                   },\n" +
                "                   \"attestation\": \"direct\",\n" +
                "                   \"challenge\":" +
                " \"-rSQHXSQUdaK1N-La5bE-JPt6EVAW4SxX1K_tXhZ_Gk\",\n" +
                "                   \"user\": {\n" +
                "                     \"displayName\": \"testName\",\n" +
                "                     \"name\": \"credManTesting@gmail.com\",\n" +
                "                     \"id\": \"eD4o2KoXLpgegAtnM5cDhhUPvvk2\"\n" +
                "                   },\n" +
                "                   \"excludeCredentials\": [],\n" +
                "                   \"rp\": {\n" +
                "                     \"name\": \"Address Book\",\n" +
                "                     \"id\": \"addressbook-c7876.uc.r.appspot.com\"\n" +
                "                   },\n" +
                "                   \"timeout\": 60000,\n" +
                "                   \"pubKeyCredParams\": [\n" +
                "                     {\n" +
                "                       \"type\": \"public-key\",\n" +
                "                       \"alg\": -7\n" +
                "                     },\n" +
                "                     {\n" +
                "                       \"type\": \"public-key\",\n" +
                "                       \"alg\": -257\n" +
                "                     },\n" +
                "                     {\n" +
                "                       \"type\": \"public-key\",\n" +
                "                       \"alg\": -37\n" +
                "                     }\n" +
                "                   ],\n" +
                "                   \"authenticatorSelection\": {\n" +
                "                     \"residentKey\": \"required\",\n" +
                "                     \"requireResidentKey\": true\n" +
                "                   }}",
            preferImmediatelyAvailableCredentials = true,
        )
        val credentialData = request.credentialData
        return RequestInfo.newCreateRequestInfo(
                Binder(),
                CreateCredentialRequest.Builder("androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL",
                credentialData, Bundle())
                        .setIsSystemProviderRequired(false)
                        .setAlwaysSendAppInfoToProvider(true)
                        .build(),
                "com.google.android.youtube"
        )
    }

    private fun testCreatePasswordRequestInfo(): RequestInfo {
        val request = CreatePasswordRequest("beckett-bakert@gmail.com", "password123")
        return RequestInfo.newCreateRequestInfo(
                Binder(),
                CreateCredentialRequest.Builder(TYPE_PASSWORD_CREDENTIAL,
                request.credentialData, request.candidateQueryData)
                        .setIsSystemProviderRequired(false)
                        .setAlwaysSendAppInfoToProvider(true)
                        .build(),
                "com.google.android.youtube"
        )
    }

    private fun testCreateOtherCredentialRequestInfo(): RequestInfo {
        val data = Bundle()
        val displayInfo = DisplayInfo("my-username00", "Joe")
        data.putBundle(
            "androidx.credentials.BUNDLE_KEY_REQUEST_DISPLAY_INFO",
            displayInfo.toBundle()
        )
        return RequestInfo.newCreateRequestInfo(
                Binder(),
                CreateCredentialRequest.Builder("other-sign-ins", data, Bundle())
                        .setIsSystemProviderRequired(false)
                        .setAlwaysSendAppInfoToProvider(true)
                        .build(),
                "com.google.android.youtube"
        )
    }

    private fun testGetRequestInfo(): RequestInfo {
        val passwordOption = GetPasswordOption()
        val passkeyOption = GetPublicKeyCredentialOption(
            "json", preferImmediatelyAvailableCredentials = false)
        return RequestInfo.newGetRequestInfo(
            Binder(),
            GetCredentialRequest.Builder(
                Bundle()
            ).addCredentialOption(
                CredentialOption.Builder(
                    passwordOption.type,
                    passwordOption.requestData,
                    passwordOption.candidateQueryData,
                ).setIsSystemProviderRequired(passwordOption.isSystemProviderRequired)
                .build()
            ).addCredentialOption(
                CredentialOption.Builder(
                    passkeyOption.type,
                    passkeyOption.requestData,
                    passkeyOption.candidateQueryData,
                ).setIsSystemProviderRequired(passkeyOption.isSystemProviderRequired)
                .build()
            ).build(),
            "com.google.android.youtube"
        )
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -111,7 +111,7 @@ class CredentialSelectorActivity : ComponentActivity() {
            ?: return Triple(false, false, null)
        if (viewModel != null && !viewModel.shouldCancelCurrentUi(cancelUiRequest.token)) {
            // Cancellation was for a different request, don't cancel the current UI.
            return Triple(false, false, null)
            return Triple(true, false, null)
        }
        val shouldShowCancellationUi = cancelUiRequest.shouldShowCancellationUi()
        Log.d(
+6 −6
Original line number Diff line number Diff line
@@ -69,7 +69,7 @@ class CredentialSelectorViewModel(

    init{
        uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_INIT,
                credManRepo.requestInfo.appPackageName)
                credManRepo.requestInfo?.appPackageName)
    }

    /**************************************************************************/
@@ -97,10 +97,10 @@ class CredentialSelectorViewModel(
        this.credManRepo = credManRepo
        uiState = credManRepo.initState()

        if (this.credManRepo.requestInfo.token != credManRepo.requestInfo.token) {
        if (this.credManRepo.requestInfo?.token != credManRepo.requestInfo?.token) {
            this.uiMetrics.resetInstanceId()
            this.uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_NEW_REQUEST,
                    credManRepo.requestInfo.appPackageName)
                    credManRepo.requestInfo?.appPackageName)
        }
    }

@@ -174,14 +174,14 @@ class CredentialSelectorViewModel(
    private fun onInternalError() {
        Log.w(Constants.LOG_TAG, "UI closed due to illegal internal state")
        this.uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_INTERNAL_ERROR,
                credManRepo.requestInfo.appPackageName)
                credManRepo.requestInfo?.appPackageName)
        credManRepo.onParsingFailureCancel()
        uiState = uiState.copy(dialogState = DialogState.COMPLETE)
    }

    /** Return true if the current UI's request token matches the UI cancellation request token. */
    fun shouldCancelCurrentUi(cancelRequestToken: IBinder): Boolean {
        return credManRepo.requestInfo.token.equals(cancelRequestToken)
        return credManRepo.requestInfo?.token?.equals(cancelRequestToken) ?: false
    }

    /**************************************************************************/
@@ -405,6 +405,6 @@ class CredentialSelectorViewModel(

    @Composable
    fun logUiEvent(uiEventEnum: UiEventEnum) {
        this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo.appPackageName)
        this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.appPackageName)
    }
}
 No newline at end of file
+6 −3
Original line number Diff line number Diff line
@@ -188,11 +188,11 @@ class GetFlowUtils {
        }

        fun toRequestDisplayInfo(
            requestInfo: RequestInfo,
            requestInfo: RequestInfo?,
            context: Context,
            originName: String?,
        ): com.android.credentialmanager.getflow.RequestDisplayInfo? {
            val getCredentialRequest = requestInfo.getCredentialRequest ?: return null
            val getCredentialRequest = requestInfo?.getCredentialRequest ?: return null
            val preferImmediatelyAvailableCredentials = getCredentialRequest.credentialOptions.any {
                val credentialOptionJetpack = CredentialOption.createFrom(
                    it.type,
@@ -450,10 +450,13 @@ class CreateFlowUtils {
        }

        fun toRequestDisplayInfo(
            requestInfo: RequestInfo,
            requestInfo: RequestInfo?,
            context: Context,
            originName: String?,
        ): RequestDisplayInfo? {
            if (requestInfo == null) {
                return null
            }
            val appLabel = originName
                ?: getAppLabel(context.packageManager, requestInfo.appPackageName)
                ?: return null
+0 −252

File deleted.

Preview size limit exceeded, changes collapsed.

Loading