diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index 86b47118ca6f3..3f4f178868135 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -45,7 +45,7 @@ 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.jetpack.developer.CreatePasswordRequest.Companion.toBundle +import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toCredentialDataBundle import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL import com.android.credentialmanager.jetpack.provider.Action @@ -325,7 +325,7 @@ class CredentialManagerRepo( key, subkey, CredentialEntry.toSlice(credentialEntry), - null + Intent() ) } @@ -348,7 +348,7 @@ class CredentialManagerRepo( android.service.credentials.CallingAppInfo( context.applicationInfo.packageName, SigningInfo()), TYPE_PASSWORD_CREDENTIAL, - toBundle("beckett-bakert@gmail.com", "password123") + toCredentialDataBundle("beckett-bakert@gmail.com", "password123") ) val fillInIntent = Intent().putExtra( CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST, @@ -417,7 +417,7 @@ class CredentialManagerRepo( " \"residentKey\": \"required\",\n" + " \"requireResidentKey\": true\n" + " }}") - val credentialData = request.data + val credentialData = request.credentialData return RequestInfo.newCreateRequestInfo( Binder(), CreateCredentialRequest( @@ -432,7 +432,7 @@ class CredentialManagerRepo( } private fun testCreatePasswordRequestInfo(): RequestInfo { - val data = toBundle("beckett-bakert@gmail.com", "password123") + val data = toCredentialDataBundle("beckett-bakert@gmail.com", "password123") return RequestInfo.newCreateRequestInfo( Binder(), CreateCredentialRequest( diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 9108f57d2f4c7..3cd42171d3327 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -242,7 +242,7 @@ class CreateFlowUtils { packageName = it.providerFlattenedComponentName } val pkgInfo = packageManager - .getPackageInfo(packageName, + .getPackageInfo(packageName!!, PackageManager.PackageInfoFlags.of(0)) DisabledProviderInfo( icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!, @@ -264,7 +264,7 @@ class CreateFlowUtils { val createCredentialRequest = requestInfo.createCredentialRequest val createCredentialRequestJetpack = createCredentialRequest?.let { CreateCredentialRequest.createFrom( - it + it.type, it.credentialData, it.candidateQueryData, it.requireSystemProvider() ) } when (createCredentialRequestJetpack) { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt index 008e1b6317ded..eaa2ad4b25b42 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt @@ -18,6 +18,7 @@ package com.android.credentialmanager.jetpack.developer import android.credentials.Credential import android.os.Bundle +import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.BUNDLE_KEY_SUBTYPE /** * Base request class for registering a credential. @@ -28,27 +29,44 @@ import android.os.Bundle * otherwise */ open class CreateCredentialRequest( - val type: String, - val data: Bundle, - val requireSystemProvider: Boolean, + open val type: String, + open val credentialData: Bundle, + open val candidateQueryData: Bundle, + open val requireSystemProvider: Boolean ) { companion object { @JvmStatic - fun createFrom(from: android.credentials.CreateCredentialRequest): CreateCredentialRequest { + fun createFrom( + type: String, + credentialData: Bundle, + candidateQueryData: Bundle, + requireSystemProvider: Boolean + ): CreateCredentialRequest { return try { - when (from.type) { + when (type) { Credential.TYPE_PASSWORD_CREDENTIAL -> - CreatePasswordRequest.createFrom(from.credentialData) + CreatePasswordRequest.createFrom(credentialData) PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL -> - CreatePublicKeyCredentialBaseRequest.createFrom(from.credentialData) - else -> - CreateCredentialRequest( - from.type, from.credentialData, from.requireSystemProvider() - ) + when (credentialData.getString(BUNDLE_KEY_SUBTYPE)) { + CreatePublicKeyCredentialRequest + .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST -> + CreatePublicKeyCredentialRequest.createFrom(credentialData) + CreatePublicKeyCredentialRequestPrivileged + .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV -> + CreatePublicKeyCredentialRequestPrivileged + .createFrom(credentialData) + else -> throw FrameworkClassParsingException() + } + else -> throw FrameworkClassParsingException() } } catch (e: FrameworkClassParsingException) { - CreateCredentialRequest( - from.type, from.credentialData, from.requireSystemProvider() + // Parsing failed but don't crash the process. Instead just output a request with + // the raw framework values. + CreateCustomCredentialRequest( + type, + credentialData, + candidateQueryData, + requireSystemProvider ) } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCustomCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCustomCredentialRequest.kt new file mode 100644 index 0000000000000..50da9a1b4000e --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCustomCredentialRequest.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 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.jetpack.developer + +import android.os.Bundle + +/** + * Base custom create request class for registering a credential. + * + * An application can construct a subtype custom request and call + * [CredentialManager.executeCreateCredential] to launch framework UI flows to collect consent and + * any other metadata needed from the user to register a new user credential. + * + * @property type the credential type determined by the credential-type-specific subclass for custom + * use cases + * @property credentialData the full credential creation request data in the [Bundle] format for + * custom use cases + * @property candidateQueryData the partial request data in the [Bundle] format that will be sent to + * the provider during the initial candidate query stage, which should not contain sensitive user + * credential information + * @property requireSystemProvider true if must only be fulfilled by a system provider and false + * otherwise + * @throws IllegalArgumentException If [type] is empty + * @throws NullPointerException If [type] or [credentialData] are null + */ +open class CreateCustomCredentialRequest( + final override val type: String, + final override val credentialData: Bundle, + final override val candidateQueryData: Bundle, + @get:JvmName("requireSystemProvider") + final override val requireSystemProvider: Boolean +) : CreateCredentialRequest(type, credentialData, candidateQueryData, requireSystemProvider) { + init { + require(type.isNotEmpty()) { "type should not be empty" } + } +} \ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt index f0da9f9d1866f..bf0aa8aec0787 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt @@ -32,9 +32,11 @@ class CreatePasswordRequest constructor( val id: String, val password: String, ) : CreateCredentialRequest( - Credential.TYPE_PASSWORD_CREDENTIAL, - toBundle(id, password), - false, + type = Credential.TYPE_PASSWORD_CREDENTIAL, + credentialData = toCredentialDataBundle(id, password), + // No credential data should be sent during the query phase. + candidateQueryData = Bundle(), + requireSystemProvider = false, ) { init { @@ -46,7 +48,7 @@ class CreatePasswordRequest constructor( const val BUNDLE_KEY_PASSWORD = "androidx.credentials.BUNDLE_KEY_PASSWORD" @JvmStatic - internal fun toBundle(id: String, password: String): Bundle { + internal fun toCredentialDataBundle(id: String, password: String): Bundle { val bundle = Bundle() bundle.putString(BUNDLE_KEY_ID, id) bundle.putString(BUNDLE_KEY_PASSWORD, password) @@ -54,7 +56,14 @@ class CreatePasswordRequest constructor( } @JvmStatic - fun createFrom(data: Bundle): CreatePasswordRequest { + internal fun toCandidateDataBundle(id: String): Bundle { + val bundle = Bundle() + bundle.putString(BUNDLE_KEY_ID, id) + return bundle + } + + @JvmStatic + internal fun createFrom(data: Bundle): CreatePasswordRequest { try { val id = data.getString(BUNDLE_KEY_ID) val password = data.getString(BUNDLE_KEY_PASSWORD) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt deleted file mode 100644 index 37a4f76339886..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2022 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.jetpack.developer - -import android.os.Bundle - -/** - * Base request class for registering a public key credential. - * - * @property requestJson The request in JSON format - * @throws NullPointerException If [requestJson] is null. This is handled by the Kotlin runtime - * @throws IllegalArgumentException If [requestJson] is empty - * - * @hide - */ -abstract class CreatePublicKeyCredentialBaseRequest constructor( - val requestJson: String, - type: String, - data: Bundle, - requireSystemProvider: Boolean, -) : CreateCredentialRequest(type, data, requireSystemProvider) { - - init { - require(requestJson.isNotEmpty()) { "request json must not be empty" } - } - - companion object { - const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON" - const val BUNDLE_KEY_SUBTYPE = "androidx.credentials.BUNDLE_KEY_SUBTYPE" - - @JvmStatic - fun createFrom(data: Bundle): CreatePublicKeyCredentialBaseRequest { - return when (data.getString(BUNDLE_KEY_SUBTYPE)) { - CreatePublicKeyCredentialRequest - .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST -> - CreatePublicKeyCredentialRequest.createFrom(data) - CreatePublicKeyCredentialRequestPrivileged - .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED -> - CreatePublicKeyCredentialRequestPrivileged.createFrom(data) - else -> throw FrameworkClassParsingException() - } - } - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt index 2eda90b827dc2..f3d402a7534a3 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt @@ -17,50 +17,81 @@ package com.android.credentialmanager.jetpack.developer import android.os.Bundle +import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.BUNDLE_KEY_SUBTYPE /** - * A request to register a passkey from the user's public key credential provider. - * - * @property requestJson the request in JSON format - * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request, - * true by default - * @throws NullPointerException If [requestJson] or [allowHybrid] is null. This is handled by the - * Kotlin runtime - * @throws IllegalArgumentException If [requestJson] is empty - * - * @hide - */ +* A request to register a passkey from the user's public key credential provider. +* +* @property requestJson the privileged request in JSON format in the standard webauthn web json +* shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson). +* @property preferImmediatelyAvailableCredentials true if you prefer the operation to return +* immediately when there is no available passkey registration offering instead of falling back to +* discovering remote options, and false (default) otherwise +* @throws NullPointerException If [requestJson] is null +* @throws IllegalArgumentException If [requestJson] is empty +*/ class CreatePublicKeyCredentialRequest @JvmOverloads constructor( - requestJson: String, - @get:JvmName("allowHybrid") - val allowHybrid: Boolean = true -) : CreatePublicKeyCredentialBaseRequest( - requestJson, - PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, - toBundle(requestJson, allowHybrid), - false, + val requestJson: String, + @get:JvmName("preferImmediatelyAvailableCredentials") + val preferImmediatelyAvailableCredentials: Boolean = false +) : CreateCredentialRequest( + type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, + credentialData = toCredentialDataBundle(requestJson, preferImmediatelyAvailableCredentials), + // The whole request data should be passed during the query phase. + candidateQueryData = toCredentialDataBundle(requestJson, preferImmediatelyAvailableCredentials), + requireSystemProvider = false, ) { + + init { + require(requestJson.isNotEmpty()) { "requestJson must not be empty" } + } + + /** @hide */ companion object { - const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID" - const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST = - "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST" + const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS = + "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS" + internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON" + internal const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST = + "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST" @JvmStatic - internal fun toBundle(requestJson: String, allowHybrid: Boolean): Bundle { + internal fun toCredentialDataBundle( + requestJson: String, + preferImmediatelyAvailableCredentials: Boolean + ): Bundle { val bundle = Bundle() bundle.putString(BUNDLE_KEY_SUBTYPE, - BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST) + BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST) bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson) - bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid) + bundle.putBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, + preferImmediatelyAvailableCredentials) return bundle } @JvmStatic - fun createFrom(data: Bundle): CreatePublicKeyCredentialRequest { + internal fun toCandidateDataBundle( + requestJson: String, + preferImmediatelyAvailableCredentials: Boolean + ): Bundle { + val bundle = Bundle() + bundle.putString(BUNDLE_KEY_SUBTYPE, + BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST) + bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson) + bundle.putBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, + preferImmediatelyAvailableCredentials) + return bundle + } + + @Suppress("deprecation") // bundle.get() used for boolean value to prevent default + // boolean value from being returned. + @JvmStatic + internal fun createFrom(data: Bundle): CreatePublicKeyCredentialRequest { try { val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON) - val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID) - return CreatePublicKeyCredentialRequest(requestJson!!, (allowHybrid!!) as Boolean) + val preferImmediatelyAvailableCredentials = + data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS) + return CreatePublicKeyCredentialRequest(requestJson!!, + (preferImmediatelyAvailableCredentials!!) as Boolean) } catch (e: Exception) { throw FrameworkClassParsingException() } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt index 36324f83a7e57..85ab2d2fec218 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt @@ -17,45 +17,62 @@ package com.android.credentialmanager.jetpack.developer import android.os.Bundle +import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.BUNDLE_KEY_SUBTYPE /** * A privileged request to register a passkey from the user’s public key credential provider, where * the caller can modify the rp. Only callers with privileged permission, e.g. user’s default - * brower, caBLE, can use this. + * brower, caBLE, can use this. These permissions will be introduced in an upcoming release. + * TODO("Add specific permission info/annotation") * - * @property requestJson the privileged request in JSON format - * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request, - * true by default - * @property rp the expected true RP ID which will override the one in the [requestJson] - * @property clientDataHash a hash that is used to verify the [rp] Identity - * @throws NullPointerException If any of [allowHybrid], [requestJson], [rp], or [clientDataHash] is - * null. This is handled by the Kotlin runtime - * @throws IllegalArgumentException If any of [requestJson], [rp], or [clientDataHash] is empty - * - * @hide + * @property requestJson the privileged request in JSON format in the standard webauthn web json + * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson). + * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return + * immediately when there is no available passkey registration offering instead of falling back to + * discovering remote options, and false (default) otherwise + * @property relyingParty the expected true RP ID which will override the one in the [requestJson], + * where rp is defined [here](https://w3c.github.io/webauthn/#rp-id) + * @property clientDataHash a hash that is used to verify the [relyingParty] Identity + * @throws NullPointerException If any of [requestJson], [relyingParty], or [clientDataHash] is + * null + * @throws IllegalArgumentException If any of [requestJson], [relyingParty], or [clientDataHash] is + * empty */ class CreatePublicKeyCredentialRequestPrivileged @JvmOverloads constructor( - requestJson: String, - val rp: String, - val clientDataHash: String, - @get:JvmName("allowHybrid") - val allowHybrid: Boolean = true -) : CreatePublicKeyCredentialBaseRequest( + val requestJson: String, + val relyingParty: String, + val clientDataHash: String, + @get:JvmName("preferImmediatelyAvailableCredentials") + val preferImmediatelyAvailableCredentials: Boolean = false +) : CreateCredentialRequest( + type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, + credentialData = toCredentialDataBundle( requestJson, - PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, - toBundle(requestJson, rp, clientDataHash, allowHybrid), - false, + relyingParty, + clientDataHash, + preferImmediatelyAvailableCredentials + ), + // The whole request data should be passed during the query phase. + candidateQueryData = toCredentialDataBundle( + requestJson, relyingParty, clientDataHash, preferImmediatelyAvailableCredentials + ), + requireSystemProvider = false, ) { init { - require(rp.isNotEmpty()) { "rp must not be empty" } + require(requestJson.isNotEmpty()) { "requestJson must not be empty" } + require(relyingParty.isNotEmpty()) { "rp must not be empty" } require(clientDataHash.isNotEmpty()) { "clientDataHash must not be empty" } } /** A builder for [CreatePublicKeyCredentialRequestPrivileged]. */ - class Builder(var requestJson: String, var rp: String, var clientDataHash: String) { + class Builder( + private var requestJson: String, + private var relyingParty: String, + private var clientDataHash: String + ) { - private var allowHybrid: Boolean = true + private var preferImmediatelyAvailableCredentials: Boolean = false /** * Sets the privileged request in JSON format. @@ -66,23 +83,30 @@ class CreatePublicKeyCredentialRequestPrivileged @JvmOverloads constructor( } /** - * Sets whether hybrid credentials are allowed to fulfill this request, true by default. + * Sets to true if you prefer the operation to return immediately when there is no available + * passkey registration offering instead of falling back to discovering remote options, and + * false otherwise. + * + * The default value is false. */ - fun setAllowHybrid(allowHybrid: Boolean): Builder { - this.allowHybrid = allowHybrid + @Suppress("MissingGetterMatchingBuilder") + fun setPreferImmediatelyAvailableCredentials( + preferImmediatelyAvailableCredentials: Boolean + ): Builder { + this.preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials return this } /** * Sets the expected true RP ID which will override the one in the [requestJson]. */ - fun setRp(rp: String): Builder { - this.rp = rp + fun setRelyingParty(relyingParty: String): Builder { + this.relyingParty = relyingParty return this } /** - * Sets a hash that is used to verify the [rp] Identity. + * Sets a hash that is used to verify the [relyingParty] Identity. */ fun setClientDataHash(clientDataHash: String): Builder { this.clientDataHash = clientDataHash @@ -91,49 +115,65 @@ class CreatePublicKeyCredentialRequestPrivileged @JvmOverloads constructor( /** Builds a [CreatePublicKeyCredentialRequestPrivileged]. */ fun build(): CreatePublicKeyCredentialRequestPrivileged { - return CreatePublicKeyCredentialRequestPrivileged(this.requestJson, - this.rp, this.clientDataHash, this.allowHybrid) + return CreatePublicKeyCredentialRequestPrivileged( + this.requestJson, + this.relyingParty, this.clientDataHash, this.preferImmediatelyAvailableCredentials + ) } } + /** @hide */ companion object { - const val BUNDLE_KEY_RP = "androidx.credentials.BUNDLE_KEY_RP" - const val BUNDLE_KEY_CLIENT_DATA_HASH = - "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH" - const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID" - const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED = - "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_" + - "PRIVILEGED" + internal const val BUNDLE_KEY_RELYING_PARTY = + "androidx.credentials.BUNDLE_KEY_RELYING_PARTY" + internal const val BUNDLE_KEY_CLIENT_DATA_HASH = + "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH" + internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS = + "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS" + + internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON" + + internal const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV = + "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_" + + "PRIVILEGED" @JvmStatic - internal fun toBundle( - requestJson: String, - rp: String, - clientDataHash: String, - allowHybrid: Boolean + internal fun toCredentialDataBundle( + requestJson: String, + relyingParty: String, + clientDataHash: String, + preferImmediatelyAvailableCredentials: Boolean ): Bundle { val bundle = Bundle() - bundle.putString(BUNDLE_KEY_SUBTYPE, - BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED) + bundle.putString( + PublicKeyCredential.BUNDLE_KEY_SUBTYPE, + BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV + ) bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson) - bundle.putString(BUNDLE_KEY_RP, rp) + bundle.putString(BUNDLE_KEY_RELYING_PARTY, relyingParty) bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash) - bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid) + bundle.putBoolean( + BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, + preferImmediatelyAvailableCredentials + ) return bundle } + @Suppress("deprecation") // bundle.get() used for boolean value to prevent default + // boolean value from being returned. @JvmStatic - fun createFrom(data: Bundle): CreatePublicKeyCredentialRequestPrivileged { + internal fun createFrom(data: Bundle): CreatePublicKeyCredentialRequestPrivileged { try { val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON) - val rp = data.getString(BUNDLE_KEY_RP) + val rp = data.getString(BUNDLE_KEY_RELYING_PARTY) val clientDataHash = data.getString(BUNDLE_KEY_CLIENT_DATA_HASH) - val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID) + val preferImmediatelyAvailableCredentials = + data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS) return CreatePublicKeyCredentialRequestPrivileged( - requestJson!!, - rp!!, - clientDataHash!!, - (allowHybrid!!) as Boolean, + requestJson!!, + rp!!, + clientDataHash!!, + (preferImmediatelyAvailableCredentials!!) as Boolean, ) } catch (e: Exception) { throw FrameworkClassParsingException() diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt index ef48a778a007c..fc7b7de6f3bba 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt @@ -28,30 +28,40 @@ import android.os.Bundle * otherwise */ open class GetCredentialOption( - val type: String, - val data: Bundle, - val requireSystemProvider: Boolean, + open val type: String, + open val requestData: Bundle, + open val candidateQueryData: Bundle, + open val requireSystemProvider: Boolean, ) { companion object { @JvmStatic - fun createFrom(from: android.credentials.GetCredentialOption): GetCredentialOption { + fun createFrom( + type: String, + requestData: Bundle, + candidateQueryData: Bundle, + requireSystemProvider: Boolean + ): GetCredentialOption { return try { - when (from.type) { + when (type) { Credential.TYPE_PASSWORD_CREDENTIAL -> - GetPasswordOption.createFrom(from.credentialRetrievalData) + GetPasswordOption.createFrom(requestData) PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL -> - GetPublicKeyCredentialBaseOption.createFrom(from.credentialRetrievalData) - else -> - GetCredentialOption( - from.type, from.credentialRetrievalData, from.requireSystemProvider() - ) + when (requestData.getString(PublicKeyCredential.BUNDLE_KEY_SUBTYPE)) { + GetPublicKeyCredentialOption + .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION -> + GetPublicKeyCredentialOption.createFrom(requestData) + GetPublicKeyCredentialOptionPrivileged + .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED -> + GetPublicKeyCredentialOptionPrivileged.createFrom(requestData) + else -> throw FrameworkClassParsingException() + } + else -> throw FrameworkClassParsingException() } } catch (e: FrameworkClassParsingException) { - GetCredentialOption( - from.type, - from.credentialRetrievalData, - from.requireSystemProvider() - ) + // Parsing failed but don't crash the process. Instead just output a request with + // the raw framework values. + GetCustomCredentialOption( + type, requestData, candidateQueryData, requireSystemProvider) } } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt index 7f9256ed6c756..18d5089828d61 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt @@ -24,7 +24,7 @@ package com.android.credentialmanager.jetpack.developer * @throws IllegalArgumentException If [getCredentialOptions] is empty */ class GetCredentialRequest constructor( - val getCredentialOptions: List, + val getCredentialOptions: List, ) { init { @@ -61,7 +61,14 @@ class GetCredentialRequest constructor( @JvmStatic fun createFrom(from: android.credentials.GetCredentialRequest): GetCredentialRequest { return GetCredentialRequest( - from.getCredentialOptions.map {GetCredentialOption.createFrom(it)} + from.getCredentialOptions.map { + GetCredentialOption.createFrom( + it.type, + it.credentialRetrievalData, + it.candidateQueryData, + it.requireSystemProvider() + ) + } ) } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCustomCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCustomCredentialOption.kt new file mode 100644 index 0000000000000..803885cbcdd0b --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCustomCredentialOption.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 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.jetpack.developer + +import android.os.Bundle + +/** + * Allows extending custom versions of GetCredentialOptions for unique use cases. + * + * @property type the credential type determined by the credential-type-specific subclass + * generated for custom use cases + * @property requestData the request data in the [Bundle] format, generated for custom use cases + * @property candidateQueryData the partial request data in the [Bundle] format that will be sent to + * the provider during the initial candidate query stage, which should not contain sensitive user + * information + * @property requireSystemProvider true if must only be fulfilled by a system provider and false + * otherwise + * @throws IllegalArgumentException If [type] is empty + * @throws NullPointerException If [requestData] or [type] is null + */ +open class GetCustomCredentialOption( + final override val type: String, + final override val requestData: Bundle, + final override val candidateQueryData: Bundle, + @get:JvmName("requireSystemProvider") + final override val requireSystemProvider: Boolean +) : GetCredentialOption( + type, + requestData, + candidateQueryData, + requireSystemProvider +) { + init { + require(type.isNotEmpty()) { "type should not be empty" } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt index 2facad17b04ef..2b9cfa30928cd 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt @@ -23,6 +23,7 @@ import android.os.Bundle class GetPasswordOption : GetCredentialOption( Credential.TYPE_PASSWORD_CREDENTIAL, Bundle(), + Bundle(), false, ) { companion object { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialBaseOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialBaseOption.kt deleted file mode 100644 index 9b51b306dd6b2..0000000000000 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialBaseOption.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2022 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.jetpack.developer - -import android.os.Bundle - -/** - * Base request class for getting a registered public key credential. - * - * @property requestJson the request in JSON format - * @throws NullPointerException If [requestJson] is null - auto handled by the - * Kotlin runtime - * @throws IllegalArgumentException If [requestJson] is empty - * - * @hide - */ -abstract class GetPublicKeyCredentialBaseOption constructor( - val requestJson: String, - type: String, - data: Bundle, - requireSystemProvider: Boolean, -) : GetCredentialOption(type, data, requireSystemProvider) { - - init { - require(requestJson.isNotEmpty()) { "request json must not be empty" } - } - - companion object { - const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON" - const val BUNDLE_KEY_SUBTYPE = "androidx.credentials.BUNDLE_KEY_SUBTYPE" - - @JvmStatic - fun createFrom(data: Bundle): GetPublicKeyCredentialBaseOption { - return when (data.getString(BUNDLE_KEY_SUBTYPE)) { - GetPublicKeyCredentialOption - .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION -> - GetPublicKeyCredentialOption.createFrom(data) - GetPublicKeyCredentialOptionPrivileged - .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED -> - GetPublicKeyCredentialOptionPrivileged.createFrom(data) - else -> throw FrameworkClassParsingException() - } - } - } -} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt index 6f13c17f9b6e3..2f9b24936e24e 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt @@ -21,44 +21,62 @@ import android.os.Bundle /** * A request to get passkeys from the user's public key credential provider. * - * @property requestJson the request in JSON format - * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request, - * true by default - * @throws NullPointerException If [requestJson] or [allowHybrid] is null. It is handled by the - * Kotlin runtime + * @property requestJson the privileged request in JSON format in the standard webauthn web json + * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson). + * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return + * immediately when there is no available credential instead of falling back to discovering remote + * credentials, and false (default) otherwise + * @throws NullPointerException If [requestJson] is null * @throws IllegalArgumentException If [requestJson] is empty - * - * @hide */ class GetPublicKeyCredentialOption @JvmOverloads constructor( - requestJson: String, - @get:JvmName("allowHybrid") - val allowHybrid: Boolean = true, -) : GetPublicKeyCredentialBaseOption( - requestJson, - PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, - toBundle(requestJson, allowHybrid), - false + val requestJson: String, + @get:JvmName("preferImmediatelyAvailableCredentials") + val preferImmediatelyAvailableCredentials: Boolean = false, +) : GetCredentialOption( + type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, + requestData = toRequestDataBundle(requestJson, preferImmediatelyAvailableCredentials), + candidateQueryData = toRequestDataBundle(requestJson, preferImmediatelyAvailableCredentials), + requireSystemProvider = false ) { + init { + require(requestJson.isNotEmpty()) { "requestJson must not be empty" } + } + + /** @hide */ companion object { - const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID" - const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION = - "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION" + internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS = + "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS" + internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON" + internal const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION = + "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION" @JvmStatic - internal fun toBundle(requestJson: String, allowHybrid: Boolean): Bundle { + internal fun toRequestDataBundle( + requestJson: String, + preferImmediatelyAvailableCredentials: Boolean + ): Bundle { val bundle = Bundle() + bundle.putString( + PublicKeyCredential.BUNDLE_KEY_SUBTYPE, + BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION + ) bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson) - bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid) + bundle.putBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, + preferImmediatelyAvailableCredentials) return bundle } + @Suppress("deprecation") // bundle.get() used for boolean value to prevent default + // boolean value from being returned. @JvmStatic - fun createFrom(data: Bundle): GetPublicKeyCredentialOption { + internal fun createFrom(data: Bundle): GetPublicKeyCredentialOption { try { val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON) - val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID) - return GetPublicKeyCredentialOption(requestJson!!, (allowHybrid!!) as Boolean) + val preferImmediatelyAvailableCredentials = + data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS) + return GetPublicKeyCredentialOption(requestJson!!, + (preferImmediatelyAvailableCredentials!!) as Boolean) } catch (e: Exception) { throw FrameworkClassParsingException() } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt index 79c62a1cdfbe6..6f4782a351c1f 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt @@ -21,41 +21,59 @@ import android.os.Bundle /** * A privileged request to get passkeys from the user's public key credential provider. The caller * can modify the RP. Only callers with privileged permission (e.g. user's public browser or caBLE) - * can use this. + * can use this. These permissions will be introduced in an upcoming release. + * TODO("Add specific permission info/annotation") * - * @property requestJson the privileged request in JSON format - * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request, - * true by default - * @property rp the expected true RP ID which will override the one in the [requestJson] - * @property clientDataHash a hash that is used to verify the [rp] Identity - * @throws NullPointerException If any of [allowHybrid], [requestJson], [rp], or [clientDataHash] - * is null. This is handled by the Kotlin runtime - * @throws IllegalArgumentException If any of [requestJson], [rp], or [clientDataHash] is empty - * - * @hide + * @property requestJson the privileged request in JSON format in the standard webauthn web json + * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson). + * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return + * immediately when there is no available credential instead of falling back to discovering remote + * credentials, and false (default) otherwise + * @property relyingParty the expected true RP ID which will override the one in the [requestJson], + * where relyingParty is defined [here](https://w3c.github.io/webauthn/#rp-id) in more detail + * @property clientDataHash a hash that is used to verify the [relyingParty] Identity + * @throws NullPointerException If any of [requestJson], [relyingParty], or [clientDataHash] + * is null + * @throws IllegalArgumentException If any of [requestJson], [relyingParty], or [clientDataHash] is + * empty */ class GetPublicKeyCredentialOptionPrivileged @JvmOverloads constructor( - requestJson: String, - val rp: String, - val clientDataHash: String, - @get:JvmName("allowHybrid") - val allowHybrid: Boolean = true -) : GetPublicKeyCredentialBaseOption( + val requestJson: String, + val relyingParty: String, + val clientDataHash: String, + @get:JvmName("preferImmediatelyAvailableCredentials") + val preferImmediatelyAvailableCredentials: Boolean = false +) : GetCredentialOption( + type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, + requestData = toBundle( requestJson, - PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, - toBundle(requestJson, rp, clientDataHash, allowHybrid), - false, + relyingParty, + clientDataHash, + preferImmediatelyAvailableCredentials + ), + candidateQueryData = toBundle( + requestJson, + relyingParty, + clientDataHash, + preferImmediatelyAvailableCredentials + ), + requireSystemProvider = false, ) { init { - require(rp.isNotEmpty()) { "rp must not be empty" } + require(requestJson.isNotEmpty()) { "requestJson must not be empty" } + require(relyingParty.isNotEmpty()) { "rp must not be empty" } require(clientDataHash.isNotEmpty()) { "clientDataHash must not be empty" } } /** A builder for [GetPublicKeyCredentialOptionPrivileged]. */ - class Builder(var requestJson: String, var rp: String, var clientDataHash: String) { + class Builder( + private var requestJson: String, + private var relyingParty: String, + private var clientDataHash: String + ) { - private var allowHybrid: Boolean = true + private var preferImmediatelyAvailableCredentials: Boolean = false /** * Sets the privileged request in JSON format. @@ -66,23 +84,30 @@ class GetPublicKeyCredentialOptionPrivileged @JvmOverloads constructor( } /** - * Sets whether hybrid credentials are allowed to fulfill this request, true by default. + * Sets to true if you prefer the operation to return immediately when there is no available + * credential instead of falling back to discovering remote credentials, and false + * otherwise. + * + * The default value is false. */ - fun setAllowHybrid(allowHybrid: Boolean): Builder { - this.allowHybrid = allowHybrid + @Suppress("MissingGetterMatchingBuilder") + fun setPreferImmediatelyAvailableCredentials( + preferImmediatelyAvailableCredentials: Boolean + ): Builder { + this.preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials return this } /** * Sets the expected true RP ID which will override the one in the [requestJson]. */ - fun setRp(rp: String): Builder { - this.rp = rp + fun setRelyingParty(relyingParty: String): Builder { + this.relyingParty = relyingParty return this } /** - * Sets a hash that is used to verify the [rp] Identity. + * Sets a hash that is used to verify the [relyingParty] Identity. */ fun setClientDataHash(clientDataHash: String): Builder { this.clientDataHash = clientDataHash @@ -91,47 +116,63 @@ class GetPublicKeyCredentialOptionPrivileged @JvmOverloads constructor( /** Builds a [GetPublicKeyCredentialOptionPrivileged]. */ fun build(): GetPublicKeyCredentialOptionPrivileged { - return GetPublicKeyCredentialOptionPrivileged(this.requestJson, - this.rp, this.clientDataHash, this.allowHybrid) + return GetPublicKeyCredentialOptionPrivileged( + this.requestJson, + this.relyingParty, this.clientDataHash, this.preferImmediatelyAvailableCredentials + ) } } + /** @hide */ companion object { - const val BUNDLE_KEY_RP = "androidx.credentials.BUNDLE_KEY_RP" - const val BUNDLE_KEY_CLIENT_DATA_HASH = - "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH" - const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID" - const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED = - "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION" + - "_PRIVILEGED" + internal const val BUNDLE_KEY_RELYING_PARTY = + "androidx.credentials.BUNDLE_KEY_RELYING_PARTY" + internal const val BUNDLE_KEY_CLIENT_DATA_HASH = + "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH" + internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS = + "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS" + internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON" + internal const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED = + "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION" + + "_PRIVILEGED" @JvmStatic internal fun toBundle( - requestJson: String, - rp: String, - clientDataHash: String, - allowHybrid: Boolean + requestJson: String, + relyingParty: String, + clientDataHash: String, + preferImmediatelyAvailableCredentials: Boolean ): Bundle { val bundle = Bundle() + bundle.putString( + PublicKeyCredential.BUNDLE_KEY_SUBTYPE, + BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED + ) bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson) - bundle.putString(BUNDLE_KEY_RP, rp) + bundle.putString(BUNDLE_KEY_RELYING_PARTY, relyingParty) bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash) - bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid) + bundle.putBoolean( + BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, + preferImmediatelyAvailableCredentials + ) return bundle } + @Suppress("deprecation") // bundle.get() used for boolean value to prevent default + // boolean value from being returned. @JvmStatic - fun createFrom(data: Bundle): GetPublicKeyCredentialOptionPrivileged { + internal fun createFrom(data: Bundle): GetPublicKeyCredentialOptionPrivileged { try { val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON) - val rp = data.getString(BUNDLE_KEY_RP) + val rp = data.getString(BUNDLE_KEY_RELYING_PARTY) val clientDataHash = data.getString(BUNDLE_KEY_CLIENT_DATA_HASH) - val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID) + val preferImmediatelyAvailableCredentials = + data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS) return GetPublicKeyCredentialOptionPrivileged( - requestJson!!, - rp!!, - clientDataHash!!, - (allowHybrid!!) as Boolean, + requestJson!!, + rp!!, + clientDataHash!!, + (preferImmediatelyAvailableCredentials!!) as Boolean, ) } catch (e: Exception) { throw FrameworkClassParsingException() diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt index b45a63bcf4ec6..6a811671d3e63 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt @@ -45,6 +45,8 @@ class PublicKeyCredential constructor( /** The type value for public key credential related operations. */ const val TYPE_PUBLIC_KEY_CREDENTIAL: String = "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" + /** The Bundle key value for the public key credential subtype (privileged or regular). */ + internal const val BUNDLE_KEY_SUBTYPE = "androidx.credentials.BUNDLE_KEY_SUBTYPE" const val BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON = "androidx.credentials.BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON"