Allow provider filter to be used for query phase

Test: built locally and CTS tests
Bug: 273310101

Change-Id: Id424501e7ca5a80703384134e649b056d37932d0
This commit is contained in:
Reema Bajwa
2023-03-14 05:50:37 +00:00
parent ec667ef2bc
commit 31f4ad2aaa
6 changed files with 227 additions and 14 deletions

View File

@@ -83,6 +83,7 @@ package android {
field public static final String CLEAR_APP_CACHE = "android.permission.CLEAR_APP_CACHE";
field public static final String CONFIGURE_WIFI_DISPLAY = "android.permission.CONFIGURE_WIFI_DISPLAY";
field public static final String CONTROL_LOCATION_UPDATES = "android.permission.CONTROL_LOCATION_UPDATES";
field public static final String CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS = "android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS";
field public static final String CREDENTIAL_MANAGER_SET_ORIGIN = "android.permission.CREDENTIAL_MANAGER_SET_ORIGIN";
field public static final String DELETE_CACHE_FILES = "android.permission.DELETE_CACHE_FILES";
field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
@@ -13671,8 +13672,9 @@ package android.credentials {
}
public final class CredentialOption implements android.os.Parcelable {
ctor public CredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
ctor @Deprecated public CredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
method public int describeContents();
method @NonNull public java.util.Set<android.content.ComponentName> getAllowedProviders();
method @NonNull public android.os.Bundle getCandidateQueryData();
method @NonNull public android.os.Bundle getCredentialRetrievalData();
method @NonNull public String getType();
@@ -13682,6 +13684,14 @@ package android.credentials {
field public static final String FLATTENED_REQUEST = "android.credentials.GetCredentialOption.FLATTENED_REQUEST_STRING";
}
public static final class CredentialOption.Builder {
ctor public CredentialOption.Builder(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle);
method @NonNull public android.credentials.CredentialOption.Builder addAllowedProvider(@NonNull android.content.ComponentName);
method @NonNull public android.credentials.CredentialOption build();
method @NonNull public android.credentials.CredentialOption.Builder setAllowedProviders(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.credentials.CredentialOption.Builder setIsSystemProviderRequired(boolean);
}
public class GetCredentialException extends java.lang.Exception {
ctor public GetCredentialException(@NonNull String, @Nullable String);
ctor public GetCredentialException(@NonNull String, @Nullable String, @Nullable Throwable);

View File

@@ -16,16 +16,25 @@
package android.credentials;
import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS;
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
import androidx.annotation.RequiresPermission;
import com.android.internal.util.AnnotationValidations;
import com.android.internal.util.Preconditions;
import java.util.Set;
/**
* Information about a specific type of credential to be requested during a {@link
* CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor,
@@ -65,6 +74,14 @@ public final class CredentialOption implements Parcelable {
*/
private final boolean mIsSystemProviderRequired;
/**
* A list of {@link ComponentName}s corresponding to the providers that this option must be
* queried against.
*/
@NonNull
private final ArraySet<ComponentName> mAllowedProviders;
/**
* Returns the requested credential type.
*/
@@ -105,12 +122,22 @@ public final class CredentialOption implements Parcelable {
return mIsSystemProviderRequired;
}
/**
* Returns the set of {@link ComponentName} corresponding to providers that must receive
* this option.
*/
@NonNull
public Set<ComponentName> getAllowedProviders() {
return mAllowedProviders;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mType);
dest.writeBundle(mCredentialRetrievalData);
dest.writeBundle(mCandidateQueryData);
dest.writeBoolean(mIsSystemProviderRequired);
dest.writeArraySet(mAllowedProviders);
}
@Override
@@ -125,6 +152,7 @@ public final class CredentialOption implements Parcelable {
+ ", requestData=" + mCredentialRetrievalData
+ ", candidateQueryData=" + mCandidateQueryData
+ ", isSystemProviderRequired=" + mIsSystemProviderRequired
+ ", allowedProviders=" + mAllowedProviders
+ "}";
}
@@ -139,17 +167,50 @@ public final class CredentialOption implements Parcelable {
* provider
* @throws IllegalArgumentException If type is empty.
*/
public CredentialOption(
private CredentialOption(
@NonNull String type,
@NonNull Bundle credentialRetrievalData,
@NonNull Bundle candidateQueryData,
boolean isSystemProviderRequired) {
boolean isSystemProviderRequired,
@NonNull ArraySet<ComponentName> allowedProviders) {
mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
mCredentialRetrievalData = requireNonNull(credentialRetrievalData,
"requestData must not be null");
mCandidateQueryData = requireNonNull(candidateQueryData,
"candidateQueryData must not be null");
mIsSystemProviderRequired = isSystemProviderRequired;
mAllowedProviders = requireNonNull(allowedProviders, "providerFilterSer must"
+ "not be empty");
}
/**
* Constructs a {@link CredentialOption}.
*
* @param type the requested credential type
* @param credentialRetrievalData the request data
* @param candidateQueryData the partial request data that will be sent to the provider
* during the initial credential candidate query stage
* @param isSystemProviderRequired whether the request must only be fulfilled by a system
* provider
* @throws IllegalArgumentException If type is empty, or null.
* @throws NullPointerException If {@code credentialRetrievalData}, or
* {@code candidateQueryData} is null.
*
* @deprecated replaced by Builder
*/
@Deprecated
public CredentialOption(
@NonNull String type,
@NonNull Bundle credentialRetrievalData,
@NonNull Bundle candidateQueryData,
boolean isSystemProviderRequired) {
this(
type,
credentialRetrievalData,
candidateQueryData,
isSystemProviderRequired,
new ArraySet<>()
);
}
private CredentialOption(@NonNull Parcel in) {
@@ -165,6 +226,8 @@ public final class CredentialOption implements Parcelable {
mCandidateQueryData = candidateQueryData;
AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
mIsSystemProviderRequired = isSystemProviderRequired;
mAllowedProviders = (ArraySet<ComponentName>) in.readArraySet(null);
AnnotationValidations.validate(NonNull.class, null, mAllowedProviders);
}
@NonNull
@@ -179,4 +242,108 @@ public final class CredentialOption implements Parcelable {
return new CredentialOption(in);
}
};
/** A builder for {@link CredentialOption}. */
public static final class Builder {
@NonNull
private String mType;
@NonNull
private Bundle mCredentialRetrievalData;
@NonNull
private Bundle mCandidateQueryData;
private boolean mIsSystemProviderRequired = false;
@NonNull
private ArraySet<ComponentName> mAllowedProviders = new ArraySet<>();
/**
* @param type the type of the credential option
* @param credentialRetrievalData the full request data
* @param candidateQueryData the partial request data that will be sent to the provider
* during the initial credential candidate query stage.
* @throws IllegalArgumentException If {@code type} is null, or empty
* @throws NullPointerException If {@code credentialRetrievalData}, or
* {@code candidateQueryData} is null
*/
public Builder(@NonNull String type, @NonNull Bundle credentialRetrievalData,
@NonNull Bundle candidateQueryData) {
mType = Preconditions.checkStringNotEmpty(type, "type must not be "
+ "null, or empty");
mCredentialRetrievalData = requireNonNull(credentialRetrievalData,
"credentialRetrievalData must not be null");
mCandidateQueryData = requireNonNull(candidateQueryData,
"candidateQueryData must not be null");
}
/**
* Sets a true/false value corresponding to whether this option must be serviced by
* system credentials providers only.
*/
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder setIsSystemProviderRequired(boolean isSystemProviderRequired) {
mIsSystemProviderRequired = isSystemProviderRequired;
return this;
}
/**
* Adds a provider {@link ComponentName} to be queried while gathering credentials from
* credential providers on the device.
*
* If no candidate providers are specified, all user configured and system credential
* providers will be queried in the candidate query phase.
*
* If an invalid component name is provided, or a service corresponding to the
* component name does not exist on the device, that component name is ignored.
* If all component names are invalid, or not present on the device, no providers
* are queried and no credentials are retrieved.
*
* @throws NullPointerException If {@code allowedProvider} is null
*/
@RequiresPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)
@NonNull
public Builder addAllowedProvider(@NonNull ComponentName allowedProvider) {
mAllowedProviders.add(requireNonNull(allowedProvider,
"allowedProvider must not be null"));
return this;
}
/**
* Sets a set of provider {@link ComponentName} to be queried while gathering credentials
* from credential providers on the device.
*
* If no candidate providers are specified, all user configured and system credential
* providers will be queried in the candidate query phase.
*
* If an invalid component name is provided, or a service corresponding to the
* component name does not exist on the device, that component name is ignored.
* If all component names are invalid, or not present on the device, no providers
* are queried and no credentials are retrieved.
*
* @throws NullPointerException If {@code allowedProviders} is null, or any of its
* elements are null.
*/
@RequiresPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)
@NonNull
public Builder setAllowedProviders(@NonNull Set<ComponentName> allowedProviders) {
Preconditions.checkCollectionElementsNotNull(
allowedProviders,
/*valueName=*/ "allowedProviders");
mAllowedProviders = new ArraySet<>(allowedProviders);
return this;
}
/**
* Builds a {@link CredentialOption}.
*/
@NonNull
public CredentialOption build() {
return new CredentialOption(mType, mCredentialRetrievalData, mCandidateQueryData,
mIsSystemProviderRequired, mAllowedProviders);
}
}
}

View File

@@ -4482,6 +4482,12 @@
<permission android:name="android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE"
android:protectionLevel="signature|privileged" />
<!-- Allows specifying candidate credential providers to be queried in Credential Manager
get flows, or to be preferred as a default in the Credential Manager create flows.
<p>Protection level: normal -->
<permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS"
android:protectionLevel="normal" />
<!-- Allows a browser to invoke credential manager APIs on behalf of another RP.
<p>Protection level: normal -->
<permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ORIGIN"

View File

@@ -467,19 +467,19 @@ class CredentialManagerRepo(
GetCredentialRequest.Builder(
Bundle()
).addCredentialOption(
CredentialOption(
CredentialOption.Builder(
passwordOption.type,
passwordOption.requestData,
passwordOption.candidateQueryData,
passwordOption.isSystemProviderRequired
)
).setIsSystemProviderRequired(passwordOption.isSystemProviderRequired)
.build()
).addCredentialOption(
CredentialOption(
CredentialOption.Builder(
passkeyOption.type,
passkeyOption.requestData,
passkeyOption.candidateQueryData,
passkeyOption.isSystemProviderRequired
)
).setIsSystemProviderRequired(passkeyOption.isSystemProviderRequired)
.build()
).build(),
"com.google.android.youtube"
)

View File

@@ -16,6 +16,7 @@
package com.android.server.credentials;
import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS;
import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN;
import static android.Manifest.permission.LAUNCH_CREDENTIAL_SELECTOR;
import static android.content.Context.CREDENTIAL_SERVICE;
@@ -124,7 +125,8 @@ public final class CredentialManagerService
serviceInfos.forEach(
info -> {
services.add(
new CredentialManagerServiceImpl(this, mLock, resolvedUserId, info));
new CredentialManagerServiceImpl(this, mLock, resolvedUserId,
info));
});
return services;
}
@@ -418,6 +420,7 @@ public final class CredentialManagerService
// Check privileged permissions
mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null);
}
enforcePermissionForAllowedProviders(request);
final int userId = UserHandle.getCallingUserId();
final int callingUid = Binder.getCallingUid();
@@ -447,6 +450,9 @@ public final class CredentialManagerService
// TODO(b/273308895): implement
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
enforcePermissionForAllowedProviders(request);
return cancelTransport;
}
@@ -823,6 +829,17 @@ public final class CredentialManagerService
}
}
private void enforcePermissionForAllowedProviders(GetCredentialRequest request) {
boolean containsAllowedProviders = request.getCredentialOptions()
.stream()
.anyMatch(option -> option.getAllowedProviders() != null
&& !option.getAllowedProviders().isEmpty());
if (containsAllowedProviders) {
mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS,
null);
}
}
private void enforceCallingPackage(String callingPackage, int callingUid) {
int packageUid;
PackageManager pm = mContext.createContextAsUser(

View File

@@ -93,7 +93,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
RemoteCredentialService remoteCredentialService) {
android.credentials.GetCredentialRequest filteredRequest =
filterOptions(providerInfo.getCapabilities(),
getRequestSession.mClientRequest);
getRequestSession.mClientRequest,
providerInfo.getComponentName());
if (filteredRequest != null) {
Map<String, CredentialOption> beginGetOptionToCredentialOptionMap =
new HashMap<>();
@@ -141,17 +142,19 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
@Nullable
private static android.credentials.GetCredentialRequest filterOptions(
List<String> providerCapabilities,
android.credentials.GetCredentialRequest clientRequest
android.credentials.GetCredentialRequest clientRequest,
ComponentName componentName
) {
List<CredentialOption> filteredOptions = new ArrayList<>();
for (CredentialOption option : clientRequest.getCredentialOptions()) {
if (providerCapabilities.contains(option.getType())) {
if (providerCapabilities.contains(option.getType())
&& isProviderAllowed(option, componentName)) {
Log.i(TAG, "In createProviderRequest - capability found : "
+ option.getType());
filteredOptions.add(option);
} else {
Log.i(TAG, "In createProviderRequest - capability not "
+ "found : " + option.getType());
+ "found, or provider not allowed : " + option.getType());
}
}
if (!filteredOptions.isEmpty()) {
@@ -164,6 +167,16 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
return null;
}
private static boolean isProviderAllowed(CredentialOption option, ComponentName componentName) {
if (!option.getAllowedProviders().isEmpty() && !option.getAllowedProviders().contains(
componentName)) {
Log.d(TAG, "Provider allow list specified but does not contain this provider: "
+ componentName.flattenToString());
return false;
}
return true;
}
public ProviderGetSession(Context context,
CredentialProviderInfo info,
ProviderInternalCallback<GetCredentialResponse> callbacks,