Merge "Update the UI jetpack parsing logic."

This commit is contained in:
TreeHugger Robot
2023-01-10 02:47:11 +00:00
committed by Android (Google) Code Review
16 changed files with 476 additions and 316 deletions

View File

@@ -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(

View File

@@ -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) {

View File

@@ -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
)
}
}

View File

@@ -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" }
}
}

View File

@@ -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)

View File

@@ -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()
}
}
}
}

View File

@@ -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()
}

View File

@@ -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()

View File

@@ -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)
}
}
}

View File

@@ -24,7 +24,7 @@ package com.android.credentialmanager.jetpack.developer
* @throws IllegalArgumentException If [getCredentialOptions] is empty
*/
class GetCredentialRequest constructor(
val getCredentialOptions: List<GetCredentialOption>,
val getCredentialOptions: List<GetCredentialOption>,
) {
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()
)
}
)
}
}

View File

@@ -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" }
}
}

View File

@@ -23,6 +23,7 @@ import android.os.Bundle
class GetPasswordOption : GetCredentialOption(
Credential.TYPE_PASSWORD_CREDENTIAL,
Bundle(),
Bundle(),
false,
) {
companion object {

View File

@@ -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()
}
}
}
}

View File

@@ -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()
}

View File

@@ -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()

View File

@@ -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"