Merge "Add SetEnabledProviders API method for Credential Manager"

This commit is contained in:
Becca Hughes
2022-12-12 20:59:35 +00:00
committed by Android (Google) Code Review
7 changed files with 353 additions and 35 deletions

View File

@@ -21,6 +21,7 @@ import static java.util.Objects.requireNonNull;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.app.Activity;
import android.app.PendingIntent;
@@ -32,6 +33,7 @@ import android.os.OutcomeReceiver;
import android.os.RemoteException;
import android.util.Log;
import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -40,9 +42,9 @@ import java.util.concurrent.Executor;
* <p>Note that an application should call the Jetpack CredentialManager apis instead of directly
* calling these framework apis.
*
* <p>The CredentialManager apis launch framework UI flows for a user to
* register a new credential or to consent to a saved credential from supported credential
* providers, which can then be used to authenticate to the app.
* <p>The CredentialManager apis launch framework UI flows for a user to register a new credential
* or to consent to a saved credential from supported credential providers, which can then be used
* to authenticate to the app.
*/
@SystemService(Context.CREDENTIAL_SERVICE)
public final class CredentialManager {
@@ -76,8 +78,7 @@ public final class CredentialManager {
@NonNull Activity activity,
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<
GetCredentialResponse, GetCredentialException> callback) {
@NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
requireNonNull(request, "request must not be null");
requireNonNull(activity, "activity must not be null");
requireNonNull(executor, "executor must not be null");
@@ -90,10 +91,11 @@ public final class CredentialManager {
ICancellationSignal cancelRemote = null;
try {
cancelRemote = mService.executeGetCredential(
request,
new GetCredentialTransport(activity, executor, callback),
mContext.getOpPackageName());
cancelRemote =
mService.executeGetCredential(
request,
new GetCredentialTransport(activity, executor, callback),
mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -106,8 +108,8 @@ public final class CredentialManager {
/**
* Launches the necessary flows to register an app credential for the user.
*
* <p>The execution can potentially launch UI flows to collect user consent to creating
* or storing the new credential, etc.
* <p>The execution can potentially launch UI flows to collect user consent to creating or
* storing the new credential, etc.
*
* @param request the request specifying type(s) of credentials to get from the user
* @param activity the activity used to launch any UI needed
@@ -120,8 +122,8 @@ public final class CredentialManager {
@NonNull Activity activity,
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<
CreateCredentialResponse, CreateCredentialException> callback) {
@NonNull
OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback) {
requireNonNull(request, "request must not be null");
requireNonNull(activity, "activity must not be null");
requireNonNull(executor, "executor must not be null");
@@ -134,9 +136,11 @@ public final class CredentialManager {
ICancellationSignal cancelRemote = null;
try {
cancelRemote = mService.executeCreateCredential(request,
new CreateCredentialTransport(activity, executor, callback),
mContext.getOpPackageName());
cancelRemote =
mService.executeCreateCredential(
request,
new CreateCredentialTransport(activity, executor, callback),
mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -149,10 +153,10 @@ public final class CredentialManager {
/**
* Clears the current user credential state from all credential providers.
*
* You should invoked this api after your user signs out of your app to notify all credential
* <p>You should invoked this api after your user signs out of your app to notify all credential
* providers that any stored credential session for the given app should be cleared.
*
* A credential provider may have stored an active credential session and use it to limit
* <p>A credential provider may have stored an active credential session and use it to limit
* sign-in options for future get-credential calls. For example, it may prioritize the active
* credential over any other available credential. When your user explicitly signs out of your
* app and in order to get the holistic sign-in options the next time, you should call this API
@@ -178,9 +182,11 @@ public final class CredentialManager {
ICancellationSignal cancelRemote = null;
try {
cancelRemote = mService.clearCredentialState(request,
new ClearCredentialStateTransport(executor, callback),
mContext.getOpPackageName());
cancelRemote =
mService.clearCredentialState(
request,
new ClearCredentialStateTransport(executor, callback),
mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -190,15 +196,44 @@ public final class CredentialManager {
}
}
/**
* Sets a list of all user configurable credential providers registered on the system. This API
* is intended for settings apps.
*
* @param providers the list of enabled providers
* @param userId the user ID to configure credential manager for
* @param executor the callback will take place on this {@link Executor}
* @param callback the callback invoked when the request succeeds or fails
* @hide
*/
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public void setEnabledProviders(
@NonNull List<String> providers,
int userId,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, SetEnabledProvidersException> callback) {
requireNonNull(executor, "executor must not be null");
requireNonNull(callback, "callback must not be null");
requireNonNull(providers, "providers must not be null");
try {
mService.setEnabledProviders(
providers, userId, new SetEnabledProvidersTransport(executor, callback));
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
private final Activity mActivity;
private final Executor mExecutor;
private final OutcomeReceiver<
GetCredentialResponse, GetCredentialException> mCallback;
private final OutcomeReceiver<GetCredentialResponse, GetCredentialException> mCallback;
private GetCredentialTransport(Activity activity, Executor executor,
private GetCredentialTransport(
Activity activity,
Executor executor,
OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
mActivity = activity;
mExecutor = executor;
@@ -210,8 +245,10 @@ public final class CredentialManager {
try {
mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "startIntentSender() failed for intent:"
+ pendingIntent.getIntentSender(), e);
Log.e(
TAG,
"startIntentSender() failed for intent:" + pendingIntent.getIntentSender(),
e);
// TODO: propagate the error.
}
}
@@ -233,10 +270,12 @@ public final class CredentialManager {
private final Activity mActivity;
private final Executor mExecutor;
private final OutcomeReceiver<
CreateCredentialResponse, CreateCredentialException> mCallback;
private final OutcomeReceiver<CreateCredentialResponse, CreateCredentialException>
mCallback;
private CreateCredentialTransport(Activity activity, Executor executor,
private CreateCredentialTransport(
Activity activity,
Executor executor,
OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback) {
mActivity = activity;
mExecutor = executor;
@@ -248,8 +287,10 @@ public final class CredentialManager {
try {
mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "startIntentSender() failed for intent:"
+ pendingIntent.getIntentSender(), e);
Log.e(
TAG,
"startIntentSender() failed for intent:" + pendingIntent.getIntentSender(),
e);
// TODO: propagate the error.
}
}
@@ -266,15 +307,14 @@ public final class CredentialManager {
}
}
private static class ClearCredentialStateTransport
extends IClearCredentialStateCallback.Stub {
private static class ClearCredentialStateTransport extends IClearCredentialStateCallback.Stub {
// TODO: listen for cancellation to release callback.
private final Executor mExecutor;
private final OutcomeReceiver<Void, ClearCredentialStateException> mCallback;
private ClearCredentialStateTransport(Executor executor,
OutcomeReceiver<Void, ClearCredentialStateException> callback) {
private ClearCredentialStateTransport(
Executor executor, OutcomeReceiver<Void, ClearCredentialStateException> callback) {
mExecutor = executor;
mCallback = callback;
}
@@ -290,4 +330,32 @@ public final class CredentialManager {
() -> mCallback.onError(new ClearCredentialStateException(errorType, message)));
}
}
private static class SetEnabledProvidersTransport extends ISetEnabledProvidersCallback.Stub {
// TODO: listen for cancellation to release callback.
private final Executor mExecutor;
private final OutcomeReceiver<Void, SetEnabledProvidersException> mCallback;
private SetEnabledProvidersTransport(
Executor executor, OutcomeReceiver<Void, SetEnabledProvidersException> callback) {
mExecutor = executor;
mCallback = callback;
}
public void onResponse(Void result) {
mExecutor.execute(() -> mCallback.onResult(result));
}
@Override
public void onResponse() {
mExecutor.execute(() -> mCallback.onResult(null));
}
@Override
public void onError(String errorType, String message) {
mExecutor.execute(
() -> mCallback.onError(new SetEnabledProvidersException(errorType, message)));
}
}
}

View File

@@ -16,12 +16,15 @@
package android.credentials;
import java.util.List;
import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.IGetCredentialCallback;
import android.credentials.ISetEnabledProvidersCallback;
import android.os.ICancellationSignal;
/**
@@ -36,4 +39,6 @@ interface ICredentialManager {
@nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
@nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
void setEnabledProviders(in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,81 @@
/*
* Copyright 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 android.credentials;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
import java.util.List;
import java.util.Objects;
/**
* Sets the enabled list of credential manager providers.
*
* @hide
*/
public final class SetEnabledProvidersRequest implements Parcelable {
/** List of providers. */
@NonNull private final List<String> mProviders;
/**
* Creates a {@link SetEnabledProvidersRequest} with a list of providers. The list is made up of
* strings that are flattened component names of the service that is the credman provider.
*
* @throws NullPointerException If args are null.
*/
public SetEnabledProvidersRequest(@NonNull List<String> providers) {
Objects.requireNonNull(providers, "providers must not be null");
Preconditions.checkCollectionElementsNotNull(providers, /* valueName= */ "providers");
mProviders = providers;
}
private SetEnabledProvidersRequest(@NonNull Parcel in) {
mProviders = in.createStringArrayList();
}
public static final @NonNull Creator<SetEnabledProvidersRequest> CREATOR =
new Creator<SetEnabledProvidersRequest>() {
@Override
public SetEnabledProvidersRequest createFromParcel(Parcel in) {
return new SetEnabledProvidersRequest(in);
}
@Override
public SetEnabledProvidersRequest[] newArray(int size) {
return new SetEnabledProvidersRequest[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeStringList(mProviders);
}
/** Returns the list of flattened Credential Manager provider component names as strings. */
public @NonNull List<String> getProviderComponentNames() {
return mProviders;
}
}

View File

@@ -20,6 +20,7 @@ import static android.content.Context.CREDENTIAL_SERVICE;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.credentials.ClearCredentialStateRequest;
@@ -30,9 +31,11 @@ import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ICredentialManager;
import android.credentials.IGetCredentialCallback;
import android.credentials.ISetEnabledProvidersCallback;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.credentials.BeginCreateCredentialRequest;
@@ -207,6 +210,47 @@ public final class CredentialManagerService extends
return cancelTransport;
}
@Override
public void setEnabledProviders(
List<String> providers, int userId, ISetEnabledProvidersCallback callback) {
Log.i(TAG, "setEnabledProviders");
userId =
ActivityManager.handleIncomingUser(
Binder.getCallingPid(),
Binder.getCallingUid(),
userId,
false,
false,
"setEnabledProviders",
null);
String storedValue = String.join(":", providers);
if (!Settings.Secure.putStringForUser(
getContext().getContentResolver(),
Settings.Secure.CREDENTIAL_SERVICE,
storedValue,
userId)) {
Log.e(TAG, "Failed to store setting containing enabled providers");
try {
callback.onError(
"failed_setting_store",
"Failed to store setting containing enabled providers");
} catch (RemoteException e) {
Log.i(TAG, "Issue with invoking error response: " + e.getMessage());
// TODO: Propagate failure
}
}
// Call the callback.
try {
callback.onResponse();
} catch (RemoteException e) {
Log.i(TAG, "Issue with invoking response: " + e.getMessage());
// TODO: Propagate failure
}
}
@Override
public ICancellationSignal clearCredentialState(ClearCredentialStateRequest request,
IClearCredentialStateCallback callback, String callingPackage) {