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 17 package androidx.credentials 18 19 import android.content.ComponentName 20 import android.os.Bundle 21 import androidx.annotation.IntDef 22 import androidx.annotation.RequiresApi 23 import androidx.annotation.RestrictTo 24 import androidx.credentials.internal.FrameworkClassParsingException 25 26 /** 27 * Base class for getting a specific type of credentials. 28 * 29 * [GetCredentialRequest] will be composed of a list of [CredentialOption] subclasses to indicate 30 * the specific credential types and configurations that your app accepts. 31 * 32 * The [typePriorityHint] value helps decide where the credential will be displayed on the selector. 33 * It is used with more importance than signals like 'last recently used' but with less importance 34 * than other signals, such as the ordering of displayed accounts. It is expected to be one of the 35 * defined [PriorityHints] constants. By default, [GetCustomCredentialOption] will have 36 * [PRIORITY_DEFAULT], [GetPasswordOption] will have [PRIORITY_PASSWORD_OR_SIMILAR] and 37 * [GetPublicKeyCredentialOption] will have [PRIORITY_PASSKEY_OR_SIMILAR]. It is expected that 38 * [GetCustomCredentialOption] types will remain unchanged unless strong reasons arise and cannot 39 * ever have [PRIORITY_PASSKEY_OR_SIMILAR]. Given passkeys prevent many security threats that other 40 * credentials do not, we enforce that nothing is shown higher than passkey types in order to 41 * provide end users with the safest credentials first. See the spec 42 * [here](https://w3c.github.io/webauthn/) for more information on passkeys. 43 * 44 * @property type the credential type determined by the credential-type-specific subclass (e.g. the 45 * type for [GetPasswordOption] is [PasswordCredential.TYPE_PASSWORD_CREDENTIAL] and for 46 * [GetPublicKeyCredentialOption] is [PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL]) 47 * @property requestData the request data in the [Bundle] format 48 * @property candidateQueryData the partial request data in the [Bundle] format that will be sent to 49 * the provider during the initial candidate query stage, which will not contain sensitive user 50 * information 51 * @property isSystemProviderRequired true if must only be fulfilled by a system provider and false 52 * otherwise 53 * @property isAutoSelectAllowed whether a credential entry will be automatically chosen if it is 54 * the only one available option 55 * @property allowedProviders a set of provider service [ComponentName] allowed to receive this 56 * option (Note: a [SecurityException] will be thrown if it is set as non-empty but your app does 57 * not have android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS; empty means every 58 * provider is eligible; for API level < 34, this property will not take effect and you should 59 * control the allowed provider via 60 * [library dependencies](https://developer.android.com/training/sign-in/passkeys#add-dependencies)) 61 * @property typePriorityHint sets the priority of this entry, which defines how it appears in the 62 * credential selector, with less precedence than account ordering but more precedence than last 63 * used time; see [PriorityHints] for more information 64 */ 65 @OptIn(ExperimentalDigitalCredentialApi::class) 66 abstract class CredentialOption 67 internal constructor( 68 val type: String, 69 val requestData: Bundle, 70 val candidateQueryData: Bundle, 71 val isSystemProviderRequired: Boolean, 72 val isAutoSelectAllowed: Boolean, 73 val allowedProviders: Set<ComponentName>, 74 val typePriorityHint: @PriorityHints Int, 75 ) { 76 77 init { 78 requestData.putBoolean(BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, isAutoSelectAllowed) 79 candidateQueryData.putBoolean(BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, isAutoSelectAllowed) 80 requestData.putInt(BUNDLE_KEY_TYPE_PRIORITY_VALUE, typePriorityHint) 81 candidateQueryData.putInt(BUNDLE_KEY_TYPE_PRIORITY_VALUE, typePriorityHint) 82 } 83 84 /** Display priority hint for each type of credentials. */ 85 @Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE) 86 @Retention(AnnotationRetention.SOURCE) 87 @RestrictTo(RestrictTo.Scope.LIBRARY) 88 @IntDef( 89 value = 90 [ 91 PRIORITY_PASSKEY_OR_SIMILAR, 92 PRIORITY_OIDC_OR_SIMILAR, 93 PRIORITY_PASSWORD_OR_SIMILAR, 94 PRIORITY_DEFAULT 95 ] 96 ) 97 annotation class PriorityHints 98 99 companion object { 100 /** Value of display priority for passkeys or credentials of similar security level. */ 101 const val PRIORITY_PASSKEY_OR_SIMILAR = 100 102 /** Value of display priority for OpenID credentials or those of similar security level. */ 103 const val PRIORITY_OIDC_OR_SIMILAR = 500 104 /** Value of display priority for passwords or credentials of similar security level. */ 105 const val PRIORITY_PASSWORD_OR_SIMILAR = 1000 106 /** Default value of display priority. */ 107 const val PRIORITY_DEFAULT = 2000 108 109 internal const val BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED = 110 "androidx.credentials.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED" 111 112 internal const val BUNDLE_KEY_TYPE_PRIORITY_VALUE = 113 "androidx.credentials.BUNDLE_KEY_TYPE_PRIORITY_VALUE" 114 extractAutoSelectValuenull115 internal fun extractAutoSelectValue(data: Bundle): Boolean { 116 return data.getBoolean(BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED) 117 } 118 119 /** 120 * Parses the [option] into an instance of [CredentialOption]. 121 * 122 * It is recommended to construct a CredentialOption by directly instantiating a 123 * CredentialOption subclass, instead of using this API. This API should only be used by a 124 * small subset of system apps that reconstruct an existing object for user interactions 125 * such as collecting consents. 126 * 127 * @param option the framework CredentialOption object 128 */ 129 @RequiresApi(34) 130 @JvmStatic createFromnull131 fun createFrom(option: android.credentials.CredentialOption): CredentialOption { 132 return createFrom( 133 option.type, 134 option.credentialRetrievalData, 135 option.candidateQueryData, 136 option.isSystemProviderRequired, 137 option.allowedProviders 138 ) 139 } 140 141 /** 142 * Parses the raw data into an instance of [CredentialOption]. 143 * 144 * It is recommended to construct a CredentialOption by directly instantiating a 145 * CredentialOption subclass, instead of using this API. This API should only be used by a 146 * small subset of system apps that reconstruct an existing object for user interactions 147 * such as collecting consents. 148 * 149 * @param type matches [CredentialOption.type] 150 * @param requestData matches [CredentialOption.requestData], the request data in the 151 * [Bundle] format; this should be constructed and retrieved from the a given 152 * [CredentialOption] itself and never be created from scratch 153 * @param candidateQueryData matches [CredentialOption.candidateQueryData]; this should be 154 * constructed and retrieved from the a given [CredentialOption] itself and never be 155 * created from scratch 156 * @param requireSystemProvider matches [CredentialOption.isSystemProviderRequired] 157 * @param allowedProviders matches [CredentialOption.allowedProviders], empty means every 158 * provider is eligible 159 */ 160 @JvmStatic createFromnull161 fun createFrom( 162 type: String, 163 requestData: Bundle, 164 candidateQueryData: Bundle, 165 requireSystemProvider: Boolean, 166 allowedProviders: Set<ComponentName>, 167 ): CredentialOption { 168 return try { 169 when (type) { 170 PasswordCredential.TYPE_PASSWORD_CREDENTIAL -> 171 GetPasswordOption.createFrom( 172 requestData, 173 allowedProviders, 174 candidateQueryData 175 ) 176 PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL -> 177 when (requestData.getString(PublicKeyCredential.BUNDLE_KEY_SUBTYPE)) { 178 GetPublicKeyCredentialOption 179 .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION -> 180 GetPublicKeyCredentialOption.createFrom( 181 requestData, 182 allowedProviders, 183 candidateQueryData 184 ) 185 else -> throw FrameworkClassParsingException() 186 } 187 DigitalCredential.TYPE_DIGITAL_CREDENTIAL -> 188 GetDigitalCredentialOption.createFrom( 189 requestData = requestData, 190 candidateQueryData = candidateQueryData, 191 requireSystemProvider = requireSystemProvider, 192 allowedProviders = allowedProviders, 193 ) 194 else -> throw FrameworkClassParsingException() 195 } 196 } catch (e: FrameworkClassParsingException) { 197 // Parsing failed but don't crash the process. Instead just output a request with 198 // the raw framework values. 199 GetCustomCredentialOption( 200 requestData, 201 type, 202 candidateQueryData = candidateQueryData, 203 isSystemProviderRequired = requireSystemProvider, 204 isAutoSelectAllowed = 205 requestData.getBoolean(BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, false), 206 allowedProviders = allowedProviders, 207 typePriorityHint = 208 requestData.getInt(BUNDLE_KEY_TYPE_PRIORITY_VALUE, PRIORITY_DEFAULT), 209 ) 210 } 211 } 212 } 213 } 214