1 /* 2 * Copyright 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package androidx.credentials.provider 17 18 import android.app.PendingIntent 19 import android.content.ComponentName 20 import android.os.Bundle 21 import androidx.annotation.RestrictTo 22 import androidx.credentials.CredentialOption 23 import androidx.credentials.provider.CallingAppInfo.Companion.extractCallingAppInfo 24 import androidx.credentials.provider.CallingAppInfo.Companion.setCallingAppInfo 25 26 /** 27 * Request received by the provider after the query phase of the get flow is complete i.e. the user 28 * was presented with a list of credentials, and the user has now made a selection from the list of 29 * [CredentialEntry] presented on the selector UI. 30 * 31 * This request will be added to the intent extras of the activity invoked by the [PendingIntent] 32 * set on the [CredentialEntry] that the user selected. The request must be extracted using the 33 * [PendingIntentHandler.retrieveProviderGetCredentialRequest] helper API. 34 * 35 * @property credentialOptions the list of credential retrieval options containing the required 36 * parameters, expected to contain a single [CredentialOption] when this request is retrieved from 37 * the [android.app.Activity] invoked by the [android.app.PendingIntent] set on a 38 * [PasswordCredentialEntry] or a [PublicKeyCredentialEntry], or expected to contain multiple 39 * [CredentialOption] when this request is retrieved from the [android.app.Activity] invoked by 40 * the [android.app.PendingIntent] set on a [RemoteEntry] 41 * @property callingAppInfo information pertaining to the calling application 42 * @property biometricPromptResult the result of a Biometric Prompt authentication flow, that is 43 * propagated to the provider if the provider requested for 44 * [androidx.credentials.CredentialManager] to handle the authentication flow 45 */ 46 class ProviderGetCredentialRequest 47 @RestrictTo(RestrictTo.Scope.LIBRARY) 48 constructor( 49 val credentialOptions: List<CredentialOption>, 50 val callingAppInfo: CallingAppInfo, 51 val biometricPromptResult: BiometricPromptResult?, 52 // The source Bundle used to construct this request, if applicable 53 @get:RestrictTo(RestrictTo.Scope.LIBRARY) val sourceBundle: Bundle?, 54 ) { 55 56 /** 57 * Constructs an instance of [ProviderGetCredentialRequest] 58 * 59 * @param credentialOptions the list of credential retrieval options containing the required 60 * parameters, expected to contain a single [CredentialOption] when this request is retrieved 61 * from the [android.app.Activity] invoked by the [android.app.PendingIntent] set on a 62 * [PasswordCredentialEntry] or a [PublicKeyCredentialEntry], or expected to contain multiple 63 * [CredentialOption] when this request is retrieved from the [android.app.Activity] invoked 64 * by the [android.app.PendingIntent] set on a [RemoteEntry] 65 * @param callingAppInfo information pertaining to the calling application 66 * @param biometricPromptResult the result of a Biometric Prompt authentication flow, that is 67 * propagated to the provider if the provider requested for 68 * [androidx.credentials.CredentialManager] to handle the authentication flow 69 */ 70 @JvmOverloads 71 constructor( 72 credentialOptions: List<CredentialOption>, 73 callingAppInfo: CallingAppInfo, 74 biometricPromptResult: BiometricPromptResult? = null, 75 ) : this(credentialOptions, callingAppInfo, biometricPromptResult, null) 76 77 companion object { 78 @JvmStatic createFromnull79 internal fun createFrom( 80 options: List<CredentialOption>, 81 callingAppInfo: CallingAppInfo, 82 biometricPromptResult: BiometricPromptResult? = null, 83 sourceBundle: Bundle? 84 ): ProviderGetCredentialRequest { 85 return ProviderGetCredentialRequest( 86 options, 87 callingAppInfo, 88 biometricPromptResult, 89 sourceBundle 90 ) 91 } 92 93 private const val EXTRA_CREDENTIAL_OPTION_SIZE = 94 "androidx.credentials.provider.extra.CREDENTIAL_OPTION_SIZE" 95 private const val EXTRA_CREDENTIAL_OPTION_TYPE_PREFIX = 96 "androidx.credentials.provider.extra.CREDENTIAL_OPTION_TYPE_" 97 private const val EXTRA_CREDENTIAL_OPTION_CREDENTIAL_RETRIEVAL_DATA_PREFIX = 98 "androidx.credentials.provider.extra.CREDENTIAL_OPTION_CREDENTIAL_RETRIEVAL_DATA_" 99 private const val EXTRA_CREDENTIAL_OPTION_CANDIDATE_QUERY_DATA_PREFIX = 100 "androidx.credentials.provider.extra.CREDENTIAL_OPTION_CANDIDATE_QUERY_DATA_" 101 private const val EXTRA_CREDENTIAL_OPTION_IS_SYSTEM_PROVIDER_REQUIRED_PREFIX = 102 "androidx.credentials.provider.extra.CREDENTIAL_OPTION_IS_SYSTEM_PROVIDER_REQUIRED_" 103 private const val EXTRA_CREDENTIAL_OPTION_ALLOWED_PROVIDERS_PREFIX = 104 "androidx.credentials.provider.extra.CREDENTIAL_OPTION_ALLOWED_PROVIDERS_" 105 106 /** 107 * Helper method to convert the given [request] to a parcelable [Bundle], in case the 108 * instance needs to be sent across a process. Consumers of this method should use 109 * [fromBundle] to reconstruct the class instance back from the bundle returned here. 110 */ 111 @JvmStatic asBundlenull112 fun asBundle(request: ProviderGetCredentialRequest): Bundle { 113 val bundle = Bundle() 114 val optionSize = request.credentialOptions.size 115 bundle.putInt(EXTRA_CREDENTIAL_OPTION_SIZE, optionSize) 116 for (i in 0 until optionSize) { 117 val option = request.credentialOptions[i] 118 bundle.putString("$EXTRA_CREDENTIAL_OPTION_TYPE_PREFIX$i", option.type) 119 bundle.putBundle( 120 "$EXTRA_CREDENTIAL_OPTION_CANDIDATE_QUERY_DATA_PREFIX$i", 121 option.candidateQueryData 122 ) 123 bundle.putBundle( 124 "$EXTRA_CREDENTIAL_OPTION_CREDENTIAL_RETRIEVAL_DATA_PREFIX$i", 125 option.requestData 126 ) 127 bundle.putBoolean( 128 "$EXTRA_CREDENTIAL_OPTION_IS_SYSTEM_PROVIDER_REQUIRED_PREFIX$i", 129 option.isSystemProviderRequired 130 ) 131 bundle.putParcelableArray( 132 "$EXTRA_CREDENTIAL_OPTION_ALLOWED_PROVIDERS_PREFIX$i", 133 option.allowedProviders.toTypedArray() 134 ) 135 } 136 bundle.setCallingAppInfo(request.callingAppInfo) 137 return bundle 138 } 139 140 /** 141 * Helper method to convert a [Bundle] retrieved through [asBundle], back to an instance of 142 * [ProviderGetCredentialRequest]. 143 * 144 * Throws [IllegalArgumentException] if the conversion fails. This means that the given 145 * [bundle] does not contain a `ProviderGetCredentialRequest`. The bundle should be 146 * constructed and retrieved from [asBundle] itself and never be created from scratch to 147 * avoid the failure. 148 */ 149 @JvmStatic fromBundlenull150 fun fromBundle(bundle: Bundle): ProviderGetCredentialRequest { 151 val callingAppInfo = 152 extractCallingAppInfo(bundle) 153 ?: throw IllegalArgumentException("Bundle was missing CallingAppInfo.") 154 val optionSize = bundle.getInt(EXTRA_CREDENTIAL_OPTION_SIZE, -1) 155 if (optionSize < 0) { 156 throw IllegalArgumentException("Bundle had invalid option size as $optionSize.") 157 } 158 val options = mutableListOf<CredentialOption>() 159 for (i in 0 until optionSize) { 160 val type = 161 bundle.getString("$EXTRA_CREDENTIAL_OPTION_TYPE_PREFIX$i") 162 ?: throw IllegalArgumentException( 163 "Bundle was missing option type at index $optionSize." 164 ) 165 val candidateQueryData = 166 bundle.getBundle("$EXTRA_CREDENTIAL_OPTION_CANDIDATE_QUERY_DATA_PREFIX$i") 167 ?: throw IllegalArgumentException( 168 "Bundle was missing candidate query data at index $optionSize." 169 ) 170 val requestData = 171 bundle.getBundle("$EXTRA_CREDENTIAL_OPTION_CREDENTIAL_RETRIEVAL_DATA_PREFIX$i") 172 ?: throw IllegalArgumentException( 173 "Bundle was missing request data at index $optionSize." 174 ) 175 val isSystemProviderRequired = 176 bundle.getBoolean( 177 "$EXTRA_CREDENTIAL_OPTION_IS_SYSTEM_PROVIDER_REQUIRED_PREFIX$i", 178 false 179 ) 180 val allowedProviders = 181 try { 182 @Suppress("DEPRECATION") 183 bundle 184 .getParcelableArray( 185 "${EXTRA_CREDENTIAL_OPTION_ALLOWED_PROVIDERS_PREFIX}$i" 186 ) 187 ?.mapNotNull { it as ComponentName? } 188 ?.toSet() ?: emptySet() 189 } catch (e: Exception) { 190 emptySet() 191 } 192 options.add( 193 CredentialOption.createFrom( 194 type, 195 requestData, 196 candidateQueryData, 197 isSystemProviderRequired, 198 allowedProviders 199 ) 200 ) 201 } 202 203 return createFrom(options, callingAppInfo, biometricPromptResult = null, bundle) 204 } 205 } 206 } 207