Add CredentialDescription registry API.

Bug: 260629338
CTS-Coverage-Bug: 265212839
API-Coverage-Bug: 265212839
Test: Local Build & Deployment
Change-Id: I6c30468eacb48425f6670b4d60bdc71d57a7e648
This commit is contained in:
Omer Ozer
2022-12-12 23:18:23 +00:00
parent b7a44e8454
commit aa4b959d19
14 changed files with 1026 additions and 6 deletions

View File

@@ -0,0 +1,22 @@
/*
* Copyright 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 android.credentials;
/**
* @hide
*/
parcelable CredentialDescription;

View File

@@ -0,0 +1,139 @@
/*
* 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 android.credentials;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import android.service.credentials.CredentialEntry;
import com.android.internal.util.AnnotationValidations;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Represents the type and contained data fields of a {@link Credential}.
* @hide
*/
public final class CredentialDescription implements Parcelable {
/**
* The credential type.
*/
@NonNull
private final String mType;
/**
* The flattened JSON string that will be matched with requests.
*/
@NonNull
private final String mFlattenedRequestString;
/**
* The entry to be used in the UI.
*/
@NonNull
private final List<CredentialEntry> mCredentialEntries;
/**
* Constructs a {@link CredentialDescription}.
*
* @param type the type of the credential returned.
* @param flattenedRequestString flattened JSON string that will be matched with requests.
* @param credentialEntries a list of {@link CredentialEntry}s that have been returned
* to the developer upon credential creation.
*
* @throws IllegalArgumentException If type is empty.
*/
public CredentialDescription(@NonNull String type,
@NonNull String flattenedRequestString,
@NonNull List<CredentialEntry> credentialEntries) {
mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
mFlattenedRequestString = Preconditions.checkStringNotEmpty(flattenedRequestString);
mCredentialEntries = Objects.requireNonNull(credentialEntries);
}
private CredentialDescription(@NonNull Parcel in) {
String type = in.readString8();
String flattenedRequestString = in.readString();
List<CredentialEntry> entries = new ArrayList<>();
in.readTypedList(entries, CredentialEntry.CREATOR);
mType = type;
AnnotationValidations.validate(android.annotation.NonNull.class, null, mType);
mFlattenedRequestString = flattenedRequestString;
AnnotationValidations.validate(android.annotation.NonNull.class, null,
mFlattenedRequestString);
mCredentialEntries = entries;
AnnotationValidations.validate(android.annotation.NonNull.class, null,
mCredentialEntries);
}
public static final @NonNull Parcelable.Creator<CredentialDescription> CREATOR =
new Parcelable.Creator<CredentialDescription>() {
@Override
public CredentialDescription createFromParcel(Parcel in) {
return new CredentialDescription(in);
}
@Override
public CredentialDescription[] newArray(int size) {
return new CredentialDescription[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mType);
dest.writeString(mFlattenedRequestString);
dest.writeTypedList(mCredentialEntries, flags);
}
@NonNull
public String getType() {
return mType;
}
@NonNull
public String getFlattenedRequestString() {
return mFlattenedRequestString;
}
@NonNull
public List<CredentialEntry> getCredentialEntries() {
return mCredentialEntries;
}
@Override
public int hashCode() {
return Objects.hash(mType, mFlattenedRequestString);
}
@Override
public boolean equals(Object obj) {
return Objects.equals(mType, ((CredentialDescription) obj).getType())
&& Objects.equals(mFlattenedRequestString, ((CredentialDescription) obj).getType());
}
}

View File

@@ -62,6 +62,14 @@ public final class CredentialManager {
public static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER =
"enable_credential_manager";
/**
* Flag to enable and disable Credential Description api.
*
* @hide
*/
private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
"enable_credential_description_api";
/**
* @hide instantiated by ContextImpl.
*/
@@ -294,6 +302,112 @@ public final class CredentialManager {
true);
}
/**
* Returns whether the credential description api is enabled.
*
* @hide
*/
public static boolean isCredentialDescriptionApiEnabled() {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false);
}
/**
* Registers a {@link CredentialDescription} for an actively provisioned {@link Credential}
* a CredentialProvider has. This registry will then be used by
* {@link #executeGetCredential(GetCredentialRequest, Activity,
* CancellationSignal, Executor, OutcomeReceiver)} to determine where to
* fetch the requested {@link Credential} from.
*
*
* @param request the request data
* @param cancellationSignal an optional signal that allows for cancelling this call
* @param executor the callback will take place on this {@link Executor}
* @param callback the callback invoked when the request succeeds or fails
*
* @throws {@link UnsupportedOperationException} if the feature has not been enabled.
*
* @hide
*/
public void registerCredentialDescription(
@NonNull RegisterCredentialDescriptionRequest request,
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, RegisterCredentialDescriptionException> callback) {
if (!isCredentialDescriptionApiEnabled()) {
throw new UnsupportedOperationException("This API is not currently supported.");
}
requireNonNull(executor, "executor must not be null");
requireNonNull(callback, "callback must not be null");
if (cancellationSignal != null && cancellationSignal.isCanceled()) {
Log.w(TAG, "executeCreateCredential already canceled");
return;
}
ICancellationSignal cancelRemote = null;
try {
cancelRemote = mService.registerCredentialDescription(request,
new RegisterCredentialDescriptionTransport(executor, callback),
mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
if (cancellationSignal != null && cancelRemote != null) {
cancellationSignal.setRemote(cancelRemote);
}
}
/**
* Unregisters a {@link CredentialDescription} for an actively provisioned {@link Credential}
* that has been registered previously.
*
*
* @param request the request data
* @param cancellationSignal an optional signal that allows for cancelling this call
* @param executor the callback will take place on this {@link Executor}
* @param callback the callback invoked when the request succeeds or fails
*
* @throws {@link UnsupportedOperationException} if the feature has not been enabled.
*
* @hide
*/
public void unRegisterCredentialDescription(
@NonNull UnregisterCredentialDescriptionRequest request,
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, UnregisterCredentialDescriptionException> callback) {
if (!isCredentialDescriptionApiEnabled()) {
throw new UnsupportedOperationException("This API is not currently supported.");
}
requireNonNull(executor, "executor must not be null");
requireNonNull(callback, "callback must not be null");
if (cancellationSignal != null && cancellationSignal.isCanceled()) {
Log.w(TAG, "executeCreateCredential already canceled");
return;
}
ICancellationSignal cancelRemote = null;
try {
cancelRemote = mService.unRegisterCredentialDescription(request,
new UnregisterCredentialDescriptionTransport(executor, callback),
mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
if (cancellationSignal != null && cancelRemote != null) {
cancellationSignal.setRemote(cancelRemote);
}
}
private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
@@ -455,4 +569,54 @@ public final class CredentialManager {
() -> mCallback.onError(new SetEnabledProvidersException(errorType, message)));
}
}
private static class RegisterCredentialDescriptionTransport
extends IRegisterCredentialDescriptionCallback.Stub {
private final Executor mExecutor;
private final OutcomeReceiver<Void, RegisterCredentialDescriptionException> mCallback;
private RegisterCredentialDescriptionTransport(Executor executor,
OutcomeReceiver<Void, RegisterCredentialDescriptionException> callback) {
mExecutor = executor;
mCallback = callback;
}
@Override
public void onResponse() {
mCallback.onResult(null);
}
@Override
public void onError(String errorCode, String message) {
mExecutor.execute(
() -> mCallback.onError(new RegisterCredentialDescriptionException(errorCode,
message)));
}
}
private static class UnregisterCredentialDescriptionTransport
extends IUnregisterCredentialDescriptionCallback.Stub {
private final Executor mExecutor;
private final OutcomeReceiver<Void, UnregisterCredentialDescriptionException> mCallback;
private UnregisterCredentialDescriptionTransport(Executor executor,
OutcomeReceiver<Void, UnregisterCredentialDescriptionException> callback) {
mExecutor = executor;
mCallback = callback;
}
@Override
public void onResponse() {
mCallback.onResult(null);
}
@Override
public void onError(String errorCode, String message) {
mExecutor.execute(
() -> mCallback.onError(new UnregisterCredentialDescriptionException(errorCode,
message)));
}
}
}

View File

@@ -21,10 +21,14 @@ import java.util.List;
import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
import android.credentials.RegisterCredentialDescriptionRequest;
import android.credentials.UnregisterCredentialDescriptionRequest;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.IGetCredentialCallback;
import android.credentials.IListEnabledProvidersCallback;
import android.credentials.IRegisterCredentialDescriptionCallback;
import android.credentials.IUnregisterCredentialDescriptionCallback;
import android.credentials.ISetEnabledProvidersCallback;
import android.os.ICancellationSignal;
@@ -44,4 +48,9 @@ interface ICredentialManager {
@nullable ICancellationSignal listEnabledProviders(in IListEnabledProvidersCallback callback);
void setEnabledProviders(in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
@nullable ICancellationSignal registerCredentialDescription(in RegisterCredentialDescriptionRequest request, in IRegisterCredentialDescriptionCallback callback, String callingPackage);
@nullable ICancellationSignal unRegisterCredentialDescription(in UnregisterCredentialDescriptionRequest request, in IUnregisterCredentialDescriptionCallback callback, String callingPackage);
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 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 android.credentials;
/**
* Listener for an registerCredentialDescription request.
*
* @hide
*/
interface IRegisterCredentialDescriptionCallback {
oneway void onResponse();
oneway void onError(String errorCode, String message);
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 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 android.credentials;
/**
* Listener for an registerCredentialDescription request.
*
* @hide
*/
interface IUnregisterCredentialDescriptionCallback {
oneway void onResponse();
oneway void onError(String errorCode, String message);
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 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 android.credentials;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.CancellationSignal;
import android.os.OutcomeReceiver;
import com.android.internal.util.Preconditions;
import java.util.concurrent.Executor;
/**
* Represents an error encountered during the {@link
* CredentialManager#registerCredentialDescription(RegisterCredentialDescriptionRequest,
* CancellationSignal, Executor, OutcomeReceiver)} operation.
*
* @hide
*/
public class RegisterCredentialDescriptionException extends Exception {
@NonNull public final String errorType;
/**
* Constructs a {@link RegisterCredentialDescriptionException}.
*
* @throws IllegalArgumentException If errorType is empty.
*/
public RegisterCredentialDescriptionException(@NonNull String errorType,
@Nullable String message) {
this(errorType, message, null);
}
/**
* Constructs a {@link RegisterCredentialDescriptionException}.
*
* @throws IllegalArgumentException If errorType is empty.
*/
public RegisterCredentialDescriptionException(
@NonNull String errorType, @Nullable String message, @Nullable Throwable cause) {
super(message, cause);
this.errorType =
Preconditions
.checkStringNotEmpty(errorType, "errorType must not be empty");
}
/**
* Constructs a {@link RegisterCredentialDescriptionException}.
*
* @throws IllegalArgumentException If errorType is empty.
*/
public RegisterCredentialDescriptionException(@NonNull String errorType,
@Nullable Throwable cause) {
this(errorType, null, cause);
}
/**
* Constructs a {@link RegisterCredentialDescriptionException}.
*
* @throws IllegalArgumentException If errorType is empty.
*/
public RegisterCredentialDescriptionException(@NonNull String errorType) {
this(errorType, null, null);
}
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright 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 android.credentials;
parcelable RegisterCredentialDescriptionRequest;

View File

@@ -0,0 +1,93 @@
/*
* 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 android.credentials;
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.AnnotationValidations;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A request to register a {@link ComponentName} that contains an actively provisioned
* {@link Credential} represented by a {@link CredentialDescription}.
*
* @hide
*/
public final class RegisterCredentialDescriptionRequest implements Parcelable {
public static final String FLATTENED_REQUEST_STRING_KEY = "flattened_request_string";
@NonNull
private final List<CredentialDescription> mCredentialDescriptions;
public RegisterCredentialDescriptionRequest(
@NonNull CredentialDescription credentialDescription) {
mCredentialDescriptions = Arrays.asList(requireNonNull(credentialDescription));
}
public RegisterCredentialDescriptionRequest(
@NonNull List<CredentialDescription> credentialDescriptions) {
mCredentialDescriptions = new ArrayList<>(requireNonNull(credentialDescriptions));
}
private RegisterCredentialDescriptionRequest(@NonNull Parcel in) {
List<CredentialDescription> credentialDescriptions = new ArrayList<>();
in.readTypedList(credentialDescriptions, CredentialDescription.CREATOR);
mCredentialDescriptions = new ArrayList<>();
AnnotationValidations.validate(android.annotation.NonNull.class, null,
credentialDescriptions);
mCredentialDescriptions.addAll(credentialDescriptions);
}
public static final @NonNull Parcelable.Creator<RegisterCredentialDescriptionRequest> CREATOR =
new Parcelable.Creator<RegisterCredentialDescriptionRequest>() {
@Override
public RegisterCredentialDescriptionRequest createFromParcel(Parcel in) {
return new RegisterCredentialDescriptionRequest(in);
}
@Override
public RegisterCredentialDescriptionRequest[] newArray(int size) {
return new RegisterCredentialDescriptionRequest[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeTypedList(mCredentialDescriptions, flags);
}
@NonNull
public List<CredentialDescription> getCredentialDescriptions() {
return mCredentialDescriptions;
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 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 android.credentials;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.CancellationSignal;
import android.os.OutcomeReceiver;
import com.android.internal.util.Preconditions;
import java.util.concurrent.Executor;
/**
* Represents an error encountered during the {@link
* CredentialManager#registerCredentialDescription(RegisterCredentialDescriptionRequest,
* CancellationSignal, Executor, OutcomeReceiver)} operation.
*
* @hide
*/
public class UnregisterCredentialDescriptionException extends Exception {
@NonNull public final String errorType;
/**
* Constructs a {@link RegisterCredentialDescriptionException}.
*
* @throws IllegalArgumentException If errorType is empty.
*/
public UnregisterCredentialDescriptionException(@NonNull String errorType,
@Nullable String message) {
this(errorType, message, null);
}
/**
* Constructs a {@link RegisterCredentialDescriptionException}.
*
* @throws IllegalArgumentException If errorType is empty.
*/
public UnregisterCredentialDescriptionException(
@NonNull String errorType, @Nullable String message, @Nullable Throwable cause) {
super(message, cause);
this.errorType =
Preconditions
.checkStringNotEmpty(errorType, "errorType must not be empty");
}
/**
* Constructs a {@link RegisterCredentialDescriptionException}.
*
* @throws IllegalArgumentException If errorType is empty.
*/
public UnregisterCredentialDescriptionException(@NonNull String errorType,
@Nullable Throwable cause) {
this(errorType, null, cause);
}
/**
* Constructs a {@link RegisterCredentialDescriptionException}.
*
* @throws IllegalArgumentException If errorType is empty.
*/
public UnregisterCredentialDescriptionException(@NonNull String errorType) {
this(errorType, null, null);
}
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright 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 android.credentials;
parcelable UnregisterCredentialDescriptionRequest;

View File

@@ -0,0 +1,81 @@
/*
* 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 android.credentials;
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.AnnotationValidations;
/**
* A request to unregister a {@link ComponentName} that contains an actively provisioned
* {@link Credential} represented by a {@link CredentialDescription}. *
*
* @hide
*/
public final class UnregisterCredentialDescriptionRequest implements Parcelable {
@NonNull
private final CredentialDescription mCredentialDescription;
public UnregisterCredentialDescriptionRequest(@NonNull CredentialDescription
credentialDescription) {
mCredentialDescription = requireNonNull(credentialDescription);
}
private UnregisterCredentialDescriptionRequest(@NonNull Parcel in) {
CredentialDescription credentialDescription =
CredentialDescription.CREATOR.createFromParcel(in);
mCredentialDescription = credentialDescription;
AnnotationValidations.validate(android.annotation.NonNull.class, null,
credentialDescription);
}
public static final @NonNull Parcelable.Creator<UnregisterCredentialDescriptionRequest>
CREATOR = new Parcelable.Creator<UnregisterCredentialDescriptionRequest>() {
@Override
public UnregisterCredentialDescriptionRequest createFromParcel(Parcel in) {
return new UnregisterCredentialDescriptionRequest(in);
}
@Override
public UnregisterCredentialDescriptionRequest[] newArray(int size) {
return new UnregisterCredentialDescriptionRequest[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
mCredentialDescription.writeToParcel(dest, flags);
}
@NonNull
public CredentialDescription getCredentialDescription() {
return mCredentialDescription;
}
}

View File

@@ -0,0 +1,125 @@
/*
* 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.server.credentials;
import android.credentials.CredentialDescription;
import android.credentials.IRegisterCredentialDescriptionCallback;
import android.credentials.IUnregisterCredentialDescriptionCallback;
import android.credentials.RegisterCredentialDescriptionRequest;
import android.credentials.UnregisterCredentialDescriptionRequest;
import android.os.RemoteException;
import android.util.SparseArray;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/** Contains information on what CredentialProvider has what provisioned Credential. */
public class CredentialDescriptionRegistry {
private static final int MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS = 128;
private static SparseArray<CredentialDescriptionRegistry> sCredentialDescriptionSessionPerUser;
static {
sCredentialDescriptionSessionPerUser = new SparseArray<>();
}
// TODO(b/265992655): add a way to update CredentialRegistry when a user is removed.
/** Get and/or create a {@link CredentialDescription} for the given user id. */
public static CredentialDescriptionRegistry forUser(int userId) {
CredentialDescriptionRegistry session =
sCredentialDescriptionSessionPerUser.get(userId, null);
if (session == null) {
session = new CredentialDescriptionRegistry();
sCredentialDescriptionSessionPerUser.put(userId, session);
}
return session;
}
private Map<String, Set<CredentialDescription>> mCredentialDescriptions;
private CredentialDescriptionRegistry() {
this.mCredentialDescriptions = new HashMap<>();
}
/** Handle the given {@link RegisterCredentialDescriptionRequest} by creating
* the appropriate package name mapping. */
public void executeRegisterRequest(RegisterCredentialDescriptionRequest request,
String callingPackageName,
IRegisterCredentialDescriptionCallback callback) {
if (!mCredentialDescriptions.containsKey(callingPackageName)
&& mCredentialDescriptions.size() <= MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS) {
mCredentialDescriptions.put(callingPackageName, new HashSet<>());
}
mCredentialDescriptions.get(callingPackageName)
.addAll(request.getCredentialDescriptions());
try {
callback.onResponse();
} catch (RemoteException e) {
e.printStackTrace();
}
}
/** Handle the given {@link UnregisterCredentialDescriptionRequest} by creating
* the appropriate package name mapping. */
public void executeUnregisterRequest(
UnregisterCredentialDescriptionRequest request,
String callingPackageName,
IUnregisterCredentialDescriptionCallback callback) {
if (mCredentialDescriptions.containsKey(callingPackageName)) {
mCredentialDescriptions.get(callingPackageName)
.remove(request.getCredentialDescription());
}
try {
callback.onResponse();
} catch (RemoteException e) {
e.printStackTrace();
}
}
/** Returns package names of CredentialProviders that can satisfy a given
* {@link CredentialDescription}. */
public Set<String> filterCredentials(String flatRequestString) {
Set<String> result = new HashSet<>();
for (String componentName: mCredentialDescriptions.keySet()) {
Set<CredentialDescription> currentSet = mCredentialDescriptions.get(componentName);
for (CredentialDescription containedDescription: currentSet) {
if (flatRequestString.equals(containedDescription.getFlattenedRequestString())) {
result.add(componentName);
}
}
}
return result;
}
void evictProviderWithPackageName(String packageName) {
if (mCredentialDescriptions.containsKey(packageName)) {
mCredentialDescriptions.remove(packageName);
}
}
}

View File

@@ -36,14 +36,19 @@ import android.credentials.ICreateCredentialCallback;
import android.credentials.ICredentialManager;
import android.credentials.IGetCredentialCallback;
import android.credentials.IListEnabledProvidersCallback;
import android.credentials.IRegisterCredentialDescriptionCallback;
import android.credentials.ISetEnabledProvidersCallback;
import android.credentials.IUnregisterCredentialDescriptionCallback;
import android.credentials.ListEnabledProvidersResponse;
import android.credentials.RegisterCredentialDescriptionRequest;
import android.credentials.UnregisterCredentialDescriptionRequest;
import android.credentials.ui.IntentFactory;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.credentials.BeginCreateCredentialRequest;
import android.service.credentials.BeginGetCredentialRequest;
@@ -59,9 +64,13 @@ import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.SecureSettingsServiceNameResolver;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Entry point service for credential management.
@@ -75,6 +84,8 @@ public final class CredentialManagerService
CredentialManagerService, CredentialManagerServiceImpl> {
private static final String TAG = "CredManSysService";
private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
"enable_credential_description_api";
private final Context mContext;
@@ -164,6 +175,7 @@ public final class CredentialManagerService
if (services == null) {
return;
}
CredentialManagerServiceImpl serviceToBeRemoved = null;
for (CredentialManagerServiceImpl service : services) {
if (service != null) {
@@ -180,10 +192,14 @@ public final class CredentialManagerService
}
if (serviceToBeRemoved != null) {
removeServiceFromCache(serviceToBeRemoved, userId);
CredentialDescriptionRegistry.forUser(userId)
.evictProviderWithPackageName(serviceToBeRemoved.getServicePackageName());
}
// TODO("Iterate over system services and remove if needed")
}
@GuardedBy("mLock")
private List<CredentialManagerServiceImpl> getOrConstructSystemServiceListLock(
int resolvedUserId) {
@@ -223,6 +239,53 @@ public final class CredentialManagerService
concatenatedServices.addAll(getOrConstructSystemServiceListLock(userId));
return concatenatedServices;
}
public static boolean isCredentialDescriptionApiEnabled() {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false);
}
@SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
// to be guarded by 'service.mLock', which is the same as mLock.
private List<ProviderSession> initiateProviderSessionsWithActiveContainers(
RequestSession session,
List<String> requestOptions, Set<ComponentName> activeCredentialContainers) {
List<ProviderSession> providerSessions = new ArrayList<>();
// Invoke all services of a user to initiate a provider session
runForUser((service) -> {
if (activeCredentialContainers.contains(service.getComponentName())) {
ProviderSession providerSession = service
.initiateProviderSessionForRequestLocked(session, requestOptions);
if (providerSession != null) {
providerSessions.add(providerSession);
}
}
});
return providerSessions;
}
@NonNull
private Set<String> getMatchingProviders(GetCredentialRequest request) {
// Session for active/provisioned credential descriptions;
CredentialDescriptionRegistry registry = CredentialDescriptionRegistry
.forUser(UserHandle.getCallingUserId());
// All requested credential descriptions based on the given request.
Set<String> requestedCredentialDescriptions =
request.getGetCredentialOptions().stream().map(
getCredentialOption -> getCredentialOption
.getCredentialRetrievalData()
.getString(RegisterCredentialDescriptionRequest
.FLATTENED_REQUEST_STRING_KEY))
.collect(Collectors.toSet());
// All requested credential descriptions based on the given request.
return requestedCredentialDescriptions.stream()
.map(registry::filterCredentials)
.flatMap(
(Function<Set<String>, Stream<String>>)
Collection::stream)
.collect(Collectors.toSet());
}
@SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
// to be guarded by 'service.mLock', which is the same as mLock.
@@ -282,11 +345,11 @@ public final class CredentialManagerService
// Initiate all provider sessions
List<ProviderSession> providerSessions =
initiateProviderSessions(
session,
request.getGetCredentialOptions().stream()
.map(GetCredentialOption::getType)
.collect(Collectors.toList()));
initiateProviderSessions(
session,
request.getGetCredentialOptions().stream()
.map(GetCredentialOption::getType)
.collect(Collectors.toList()));
if (providerSessions.isEmpty()) {
try {
@@ -316,7 +379,7 @@ public final class CredentialManagerService
ICreateCredentialCallback callback,
String callingPackage) {
Log.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage);
// TODO : Implement cancellation
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
// New request session, scoped for this request only.
@@ -478,5 +541,77 @@ public final class CredentialManagerService
});
return cancelTransport;
}
@Override
public ICancellationSignal registerCredentialDescription(
RegisterCredentialDescriptionRequest request,
IRegisterCredentialDescriptionCallback callback, String callingPackage) {
Log.i(TAG, "registerCredentialDescription");
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
List<CredentialProviderInfo> services =
CredentialProviderInfo.getAvailableServices(mContext,
UserHandle.getCallingUserId());
List<String> providers = services.stream()
.map(credentialProviderInfo
-> credentialProviderInfo.getServiceInfo().packageName).toList();
if (!providers.contains(callingPackage)) {
try {
callback.onError("UNKNOWN",
"Not an existing provider.");
} catch (RemoteException e) {
Log.i(
TAG,
"Issue invoking onError on IRegisterCredentialDescriptionCallback "
+ "callback: "
+ e.getMessage());
}
}
CredentialDescriptionRegistry session = CredentialDescriptionRegistry
.forUser(UserHandle.getCallingUserId());
session.executeRegisterRequest(request, callingPackage, callback);
return cancelTransport;
}
@Override
public ICancellationSignal unRegisterCredentialDescription(
UnregisterCredentialDescriptionRequest request,
IUnregisterCredentialDescriptionCallback callback, String callingPackage) {
Log.i(TAG, "registerCredentialDescription");
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
List<CredentialProviderInfo> services =
CredentialProviderInfo.getAvailableServices(mContext,
UserHandle.getCallingUserId());
List<String> providers = services.stream()
.map(credentialProviderInfo
-> credentialProviderInfo.getServiceInfo().packageName).toList();
if (!providers.contains(callingPackage)) {
try {
callback.onError("UNKNOWN",
"Not an existing provider.");
} catch (RemoteException e) {
Log.i(
TAG,
"Issue invoking onError on IRegisterCredentialDescriptionCallback "
+ "callback: "
+ e.getMessage());
}
}
CredentialDescriptionRegistry session = CredentialDescriptionRegistry
.forUser(UserHandle.getCallingUserId());
session.executeUnregisterRequest(request, callingPackage, callback);
return cancelTransport;
}
}
}