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.graphics.drawable.Icon 20 import android.os.Bundle 21 import android.text.TextUtils 22 import androidx.annotation.RequiresApi 23 import androidx.annotation.RestrictTo 24 import androidx.credentials.PublicKeyCredential.Companion.BUNDLE_KEY_SUBTYPE 25 import androidx.credentials.internal.FrameworkClassParsingException 26 27 /** 28 * Base request class for registering a credential. 29 * 30 * An application can construct a subtype request and call [CredentialManager.createCredential] to 31 * launch framework UI flows to collect consent and any other metadata needed from the user to 32 * register a new user credential. 33 * 34 * @property type the credential type determined by the credential-type-specific subclass (e.g. the 35 * type for [CreatePasswordRequest] is [PasswordCredential.TYPE_PASSWORD_CREDENTIAL] and for 36 * [CreatePublicKeyCredentialRequest] is [PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL]) 37 * @property credentialData the request data in the [Bundle] format 38 * @property candidateQueryData the partial request data in the [Bundle] format that will be sent to 39 * the provider during the initial candidate query stage, which should not contain sensitive user 40 * credential information (note: bundle keys in the form of `androidx.credentials.*` are reserved 41 * for internal library use) 42 * @property isSystemProviderRequired true if must only be fulfilled by a system provider and false 43 * otherwise 44 * @property isAutoSelectAllowed whether a create option will be automatically chosen if it is the 45 * only one available to the user 46 * @property displayInfo the information to be displayed on the screen 47 * @property origin the origin of a different application if the request is being made on behalf of 48 * that application (Note: for API level >=34, setting a non-null value for this parameter will 49 * throw a SecurityException if android.permission.CREDENTIAL_MANAGER_SET_ORIGIN is not present) 50 * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return 51 * immediately when there is no available passkey registration offering instead of falling back to 52 * discovering remote options, and false (preferred by default) otherwise 53 */ 54 abstract class CreateCredentialRequest 55 internal constructor( 56 val type: String, 57 val credentialData: Bundle, 58 val candidateQueryData: Bundle, 59 val isSystemProviderRequired: Boolean, 60 val isAutoSelectAllowed: Boolean, 61 val displayInfo: DisplayInfo, 62 val origin: String?, 63 @get:JvmName("preferImmediatelyAvailableCredentials") 64 val preferImmediatelyAvailableCredentials: Boolean, 65 ) { 66 init { 67 credentialData.putBoolean(BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, isAutoSelectAllowed) 68 credentialData.putBoolean( 69 BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, 70 preferImmediatelyAvailableCredentials 71 ) 72 candidateQueryData.putBoolean(BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, isAutoSelectAllowed) 73 } 74 75 /** 76 * Information that may be used for display purposes when rendering UIs to collect the user 77 * consent and choice. 78 * 79 * @property userId the user identifier of the created credential 80 * @property userDisplayName an optional display name in addition to the [userId] that may be 81 * displayed next to the `userId` during the user consent to help your user better understand 82 * the credential being created 83 */ 84 class DisplayInfo 85 internal constructor( 86 val userId: CharSequence, 87 val userDisplayName: CharSequence?, 88 @get:RestrictTo(RestrictTo.Scope.LIBRARY) // used from java tests 89 val credentialTypeIcon: Icon?, 90 @get:RestrictTo(RestrictTo.Scope.LIBRARY) // used from java tests 91 val preferDefaultProvider: String?, 92 ) { 93 94 /** 95 * Constructs a [DisplayInfo]. 96 * 97 * @param userId the user id of the created credential 98 * @param userDisplayName an optional display name in addition to the [userId] that may be 99 * displayed next to the `userId` during the user consent to help your user better 100 * understand the credential being created 101 * @throws IllegalArgumentException If [userId] is empty 102 */ 103 @JvmOverloads 104 constructor( 105 userId: CharSequence, 106 userDisplayName: CharSequence? = null, 107 ) : this( 108 userId, 109 userDisplayName, 110 null, 111 null, 112 ) 113 114 /** 115 * Constructs a [DisplayInfo]. 116 * 117 * @param userId the user id of the created credential 118 * @param userDisplayName an optional display name in addition to the [userId] that may be 119 * displayed next to the `userId` during the user consent to help your user better 120 * understand the credential being created 121 * @param preferDefaultProvider the preferred default provider component name to prioritize 122 * in the selection UI flows. Your app must have the permission 123 * android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS to specify this, or it 124 * would not take effect. Also this bit may not take effect for Android API level 33 and 125 * below, depending on the pre-34 provider(s) you have chosen. 126 * @throws IllegalArgumentException If [userId] is empty 127 */ 128 constructor( 129 userId: CharSequence, 130 userDisplayName: CharSequence?, 131 preferDefaultProvider: String? 132 ) : this( 133 userId, 134 userDisplayName, 135 null, 136 preferDefaultProvider, 137 ) 138 139 init { <lambda>null140 require(userId.isNotEmpty()) { "userId should not be empty" } 141 } 142 143 @RestrictTo(RestrictTo.Scope.LIBRARY) // used from java tests 144 @RequiresApi(23) toBundlenull145 fun toBundle(): Bundle { 146 val bundle = Bundle() 147 bundle.putCharSequence(BUNDLE_KEY_USER_ID, userId) 148 if (!TextUtils.isEmpty(userDisplayName)) { 149 bundle.putCharSequence(BUNDLE_KEY_USER_DISPLAY_NAME, userDisplayName) 150 } 151 if (!TextUtils.isEmpty(preferDefaultProvider)) { 152 bundle.putString(BUNDLE_KEY_DEFAULT_PROVIDER, preferDefaultProvider) 153 } 154 // Today the type icon is determined solely within this library right before the 155 // request is passed into the framework. Later if needed a new API can be added for 156 // custom SDKs to supply their own credential type icons. 157 return bundle 158 } 159 160 companion object { 161 @RestrictTo(RestrictTo.Scope.LIBRARY) // used from java tests 162 const val BUNDLE_KEY_REQUEST_DISPLAY_INFO = 163 "androidx.credentials.BUNDLE_KEY_REQUEST_DISPLAY_INFO" 164 @RestrictTo(RestrictTo.Scope.LIBRARY) // used from java tests 165 const val BUNDLE_KEY_USER_ID = "androidx.credentials.BUNDLE_KEY_USER_ID" 166 167 internal const val BUNDLE_KEY_USER_DISPLAY_NAME = 168 "androidx.credentials.BUNDLE_KEY_USER_DISPLAY_NAME" 169 @RestrictTo(RestrictTo.Scope.LIBRARY) // used from java tests 170 const val BUNDLE_KEY_CREDENTIAL_TYPE_ICON = 171 "androidx.credentials.BUNDLE_KEY_CREDENTIAL_TYPE_ICON" 172 173 internal const val BUNDLE_KEY_DEFAULT_PROVIDER = 174 "androidx.credentials.BUNDLE_KEY_DEFAULT_PROVIDER" 175 176 /** 177 * Returns a RequestDisplayInfo from a [CreateCredentialRequest.credentialData] Bundle. 178 * 179 * It is recommended to construct a DisplayInfo by direct constructor calls, instead of 180 * using this API. This API should only be used by a small subset of system apps that 181 * reconstruct an existing object for user interactions such as collecting consents. 182 * 183 * @param from the raw display data in the Bundle format, retrieved from 184 * [CreateCredentialRequest.credentialData] 185 */ 186 @JvmStatic 187 @RequiresApi(23) // Icon dependency createFromnull188 fun createFrom(from: Bundle): DisplayInfo { 189 return try { 190 val displayInfoBundle = from.getBundle(BUNDLE_KEY_REQUEST_DISPLAY_INFO)!! 191 val userId = displayInfoBundle.getCharSequence(BUNDLE_KEY_USER_ID) 192 val displayName = 193 displayInfoBundle.getCharSequence(BUNDLE_KEY_USER_DISPLAY_NAME) 194 @Suppress("DEPRECATION") // bundle.getParcelable(key) 195 val icon: Icon? = 196 displayInfoBundle.getParcelable(BUNDLE_KEY_CREDENTIAL_TYPE_ICON) 197 val defaultProvider: String? = 198 displayInfoBundle.getString(BUNDLE_KEY_DEFAULT_PROVIDER) 199 DisplayInfo(userId!!, displayName, icon, defaultProvider) 200 } catch (e: Exception) { 201 throw IllegalArgumentException(e) 202 } 203 } 204 } 205 } 206 207 companion object { 208 @RestrictTo(RestrictTo.Scope.LIBRARY) // used from java tests 209 const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS = 210 "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS" 211 @RestrictTo(RestrictTo.Scope.LIBRARY) // used from java tests 212 const val BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED = 213 "androidx.credentials.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED" 214 215 /** 216 * Parses the [request] into an instance of [CreateCredentialRequest]. 217 * 218 * It is recommended to construct a CreateCredentialRequest by directly instantiating a 219 * CreateCredentialRequest subclass, instead of using this API. This API should only be used 220 * by a small subset of system apps that reconstruct an existing object for user 221 * interactions such as collecting consents. 222 * 223 * @param request the framework CreateCredentialRequest object 224 */ 225 @JvmStatic 226 @RequiresApi(34) createFromnull227 fun createFrom( 228 request: android.credentials.CreateCredentialRequest 229 ): CreateCredentialRequest { 230 return createFrom( 231 request.type, 232 request.credentialData, 233 request.candidateQueryData, 234 request.isSystemProviderRequired, 235 request.origin 236 ) 237 } 238 239 /** 240 * Attempts to parse the raw data into one of [CreatePasswordRequest], 241 * [CreatePublicKeyCredentialRequest], and [CreateCustomCredentialRequest]. 242 * 243 * It is recommended to construct a CreateCredentialRequest by directly instantiating a 244 * CreateCredentialRequest subclass, instead of using this API. This API should only be used 245 * by a small subset of system apps that reconstruct an existing object for user 246 * interactions such as collecting consents. 247 * 248 * @param type matches [CreateCredentialRequest.type] 249 * @param credentialData matches [CreateCredentialRequest.credentialData], the request data 250 * in the [Bundle] format; this should be constructed and retrieved from the a given 251 * [CreateCredentialRequest] itself and never be created from scratch 252 * @param candidateQueryData matches [CreateCredentialRequest.candidateQueryData], the 253 * partial request data in the [Bundle] format that will be sent to the provider during 254 * the initial candidate query stage; this should be constructed and retrieved from the a 255 * given [CreateCredentialRequest] itself and never be created from scratch 256 * @param requireSystemProvider whether the request must only be fulfilled by a system 257 * provider 258 * @param origin the origin of a different application if the request is being made on 259 * behalf of that application 260 */ 261 @JvmStatic 262 @JvmOverloads 263 @RequiresApi(23) createFromnull264 fun createFrom( 265 type: String, 266 credentialData: Bundle, 267 candidateQueryData: Bundle, 268 requireSystemProvider: Boolean, 269 origin: String? = null, 270 ): CreateCredentialRequest { 271 return try { 272 when (type) { 273 PasswordCredential.TYPE_PASSWORD_CREDENTIAL -> 274 CreatePasswordRequest.createFrom(credentialData, origin, candidateQueryData) 275 PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL -> 276 when (credentialData.getString(BUNDLE_KEY_SUBTYPE)) { 277 CreatePublicKeyCredentialRequest 278 .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST -> 279 CreatePublicKeyCredentialRequest.createFrom( 280 credentialData, 281 origin, 282 candidateQueryData 283 ) 284 else -> throw FrameworkClassParsingException() 285 } 286 else -> throw FrameworkClassParsingException() 287 } 288 } catch (e: FrameworkClassParsingException) { 289 // Parsing failed but don't crash the process. Instead just output a request with 290 // the raw framework values. 291 CreateCustomCredentialRequest( 292 type, 293 credentialData, 294 candidateQueryData, 295 requireSystemProvider, 296 DisplayInfo.createFrom(credentialData), 297 credentialData.getBoolean(BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED, false), 298 origin, 299 credentialData.getBoolean( 300 BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, 301 false 302 ), 303 ) 304 } 305 } 306 } 307 } 308