Merge "Add clear cred implementaion Test: Built locally"
This commit is contained in:
committed by
Android (Google) Code Review
commit
ec2e875d05
@@ -44,6 +44,8 @@ public class RequestInfo implements Parcelable {
|
||||
public static final @NonNull String EXTRA_REQUEST_INFO =
|
||||
"android.credentials.ui.extra.REQUEST_INFO";
|
||||
|
||||
/** Type value for any request that does not require UI. */
|
||||
public static final @NonNull String TYPE_UNDEFINED = "android.credentials.ui.TYPE_UNDEFINED";
|
||||
/** Type value for an executeGetCredential request. */
|
||||
public static final @NonNull String TYPE_GET = "android.credentials.ui.TYPE_GET";
|
||||
/** Type value for an executeCreateCredential request. */
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.annotation.Nullable;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.credentials.ClearCredentialStateRequest;
|
||||
import android.credentials.IClearCredentialStateCallback;
|
||||
import android.credentials.ui.ProviderData;
|
||||
import android.credentials.ui.RequestInfo;
|
||||
import android.os.RemoteException;
|
||||
import android.service.credentials.CredentialProviderInfo;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Central session for a single clearCredentialState request. This class listens to the
|
||||
* responses from providers, and updates the provider(S) state.
|
||||
*/
|
||||
public final class ClearRequestSession extends RequestSession<ClearCredentialStateRequest,
|
||||
IClearCredentialStateCallback>
|
||||
implements ProviderSession.ProviderInternalCallback<Void> {
|
||||
private static final String TAG = "GetRequestSession";
|
||||
|
||||
public ClearRequestSession(Context context, int userId,
|
||||
IClearCredentialStateCallback callback, ClearCredentialStateRequest request,
|
||||
String callingPackage) {
|
||||
super(context, userId, request, callback, RequestInfo.TYPE_UNDEFINED, callingPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new provider session, and adds it list of providers that are contributing to
|
||||
* this session.
|
||||
* @return the provider session created within this request session, for the given provider
|
||||
* info.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
|
||||
RemoteCredentialService remoteCredentialService) {
|
||||
ProviderClearSession providerClearSession = ProviderClearSession
|
||||
.createNewSession(mContext, mUserId, providerInfo,
|
||||
this, remoteCredentialService);
|
||||
if (providerClearSession != null) {
|
||||
Log.i(TAG, "In startProviderSession - provider session created and being added");
|
||||
mProviders.put(providerClearSession.getComponentName().flattenToString(),
|
||||
providerClearSession);
|
||||
}
|
||||
return providerClearSession;
|
||||
}
|
||||
|
||||
@Override // from provider session
|
||||
public void onProviderStatusChanged(ProviderSession.Status status,
|
||||
ComponentName componentName) {
|
||||
Log.i(TAG, "in onStatusChanged with status: " + status);
|
||||
if (ProviderSession.isTerminatingStatus(status)) {
|
||||
Log.i(TAG, "in onStatusChanged terminating status");
|
||||
onProviderTerminated(componentName);
|
||||
} else if (ProviderSession.isCompletionStatus(status)) {
|
||||
Log.i(TAG, "in onStatusChanged isCompletionStatus status");
|
||||
onProviderResponseComplete(componentName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinalResponseReceived(ComponentName componentName, Void response) {
|
||||
respondToClientWithResponseAndFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProviderResponseComplete(ComponentName componentName) {
|
||||
if (!isAnyProviderPending()) {
|
||||
onFinalResponseReceived(componentName, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProviderTerminated(ComponentName componentName) {
|
||||
if (!isAnyProviderPending()) {
|
||||
processResponses();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
|
||||
//Not applicable for clearCredential as UI is not needed
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinalErrorReceived(ComponentName componentName, String errorType,
|
||||
String message) {
|
||||
//Not applicable for clearCredential as response is not picked by the user
|
||||
}
|
||||
|
||||
private void respondToClientWithResponseAndFinish() {
|
||||
Log.i(TAG, "respondToClientWithResponseAndFinish");
|
||||
try {
|
||||
mClientCallback.onSuccess();
|
||||
} catch (RemoteException e) {
|
||||
Log.i(TAG, "Issue while propagating the response to the client");
|
||||
}
|
||||
finishSession();
|
||||
}
|
||||
|
||||
private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
|
||||
Log.i(TAG, "respondToClientWithErrorAndFinish");
|
||||
try {
|
||||
mClientCallback.onError(errorType, errorMsg);
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
finishSession();
|
||||
}
|
||||
private void processResponses() {
|
||||
for (ProviderSession session: mProviders.values()) {
|
||||
if (session.isProviderResponseSet()) {
|
||||
// If even one provider responded successfully, send back the response
|
||||
// TODO: Aggregate other exceptions
|
||||
respondToClientWithResponseAndFinish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// TODO: Replace with properly defined error type
|
||||
respondToClientWithErrorAndFinish("unknown", "All providers failed");
|
||||
}
|
||||
}
|
||||
@@ -314,9 +314,45 @@ public final class CredentialManagerService
|
||||
@Override
|
||||
public ICancellationSignal clearCredentialState(ClearCredentialStateRequest request,
|
||||
IClearCredentialStateCallback callback, String callingPackage) {
|
||||
// TODO: implement.
|
||||
Log.i(TAG, "clearCredentialSession");
|
||||
Log.i(TAG, "starting clearCredentialState with callingPackage: " + callingPackage);
|
||||
// TODO : Implement cancellation
|
||||
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
|
||||
|
||||
// New request session, scoped for this request only.
|
||||
final ClearRequestSession session =
|
||||
new ClearRequestSession(
|
||||
getContext(),
|
||||
UserHandle.getCallingUserId(),
|
||||
callback,
|
||||
request,
|
||||
callingPackage);
|
||||
|
||||
// Initiate all provider sessions
|
||||
// TODO: Determine if provider needs to have clear capability in their manifest
|
||||
List<ProviderSession> providerSessions =
|
||||
initiateProviderSessions(session, List.of());
|
||||
|
||||
if (providerSessions.isEmpty()) {
|
||||
try {
|
||||
// TODO("Replace with properly defined error type")
|
||||
callback.onError("unknown_type",
|
||||
"No providers available to fulfill request.");
|
||||
} catch (RemoteException e) {
|
||||
Log.i(TAG, "Issue invoking onError on IClearCredentialStateCallback "
|
||||
+ "callback: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over all provider sessions and invoke the request
|
||||
providerSessions.forEach(
|
||||
providerClearSession -> {
|
||||
providerClearSession
|
||||
.getRemoteCredentialService()
|
||||
.onClearCredentialState(
|
||||
(android.service.credentials.ClearCredentialStateRequest)
|
||||
providerClearSession.getProviderRequest(),
|
||||
/* callback= */ providerClearSession);
|
||||
});
|
||||
return cancelTransport;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public final class CredentialManagerServiceImpl extends
|
||||
@GuardedBy("mLock")
|
||||
public ProviderSession initiateProviderSessionForRequestLocked(
|
||||
RequestSession requestSession, List<String> requestOptions) {
|
||||
if (!isServiceCapableLocked(requestOptions)) {
|
||||
if (!requestOptions.isEmpty() && !isServiceCapableLocked(requestOptions)) {
|
||||
Log.i(TAG, "Service is not capable");
|
||||
return null;
|
||||
}
|
||||
@@ -88,9 +88,7 @@ public final class CredentialManagerServiceImpl extends
|
||||
}
|
||||
final RemoteCredentialService remoteService = new RemoteCredentialService(
|
||||
getContext(), mInfo.getServiceInfo().getComponentName(), mUserId);
|
||||
ProviderSession providerSession =
|
||||
requestSession.initiateProviderSession(mInfo, remoteService);
|
||||
return providerSession;
|
||||
return requestSession.initiateProviderSession(mInfo, remoteService);
|
||||
}
|
||||
|
||||
/** Return true if at least one capability found. */
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.Context;
|
||||
import android.credentials.ClearCredentialStateException;
|
||||
import android.credentials.ui.ProviderData;
|
||||
import android.credentials.ui.ProviderPendingIntentResponse;
|
||||
import android.service.credentials.CallingAppInfo;
|
||||
import android.service.credentials.ClearCredentialStateRequest;
|
||||
import android.service.credentials.CredentialProviderInfo;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
/**
|
||||
* Central provider session that listens for provider callbacks, and maintains provider state.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class ProviderClearSession extends ProviderSession<ClearCredentialStateRequest,
|
||||
Void>
|
||||
implements
|
||||
RemoteCredentialService.ProviderCallbacks<Void> {
|
||||
private static final String TAG = "ProviderClearSession";
|
||||
|
||||
private ClearCredentialStateException mProviderException;
|
||||
|
||||
/** Creates a new provider session to be used by the request session. */
|
||||
@Nullable public static ProviderClearSession createNewSession(
|
||||
Context context,
|
||||
@UserIdInt int userId,
|
||||
CredentialProviderInfo providerInfo,
|
||||
ClearRequestSession clearRequestSession,
|
||||
RemoteCredentialService remoteCredentialService) {
|
||||
ClearCredentialStateRequest providerRequest =
|
||||
createProviderRequest(
|
||||
clearRequestSession.mClientRequest,
|
||||
clearRequestSession.mClientCallingPackage);
|
||||
return new ProviderClearSession(context, providerInfo, clearRequestSession, userId,
|
||||
remoteCredentialService, providerRequest);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ClearCredentialStateRequest createProviderRequest(
|
||||
android.credentials.ClearCredentialStateRequest clientRequest,
|
||||
String clientCallingPackage
|
||||
) {
|
||||
// TODO: Determine if provider needs to declare clear capability in manifest
|
||||
return new ClearCredentialStateRequest(
|
||||
new CallingAppInfo(clientCallingPackage, new ArraySet<>()),
|
||||
clientRequest.getData());
|
||||
}
|
||||
|
||||
public ProviderClearSession(Context context,
|
||||
CredentialProviderInfo info,
|
||||
ProviderInternalCallback callbacks,
|
||||
int userId, RemoteCredentialService remoteCredentialService,
|
||||
ClearCredentialStateRequest providerRequest) {
|
||||
super(context, info, providerRequest, callbacks, userId, remoteCredentialService);
|
||||
setStatus(Status.PENDING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProviderResponseSuccess(@Nullable Void response) {
|
||||
Log.i(TAG, "in onProviderResponseSuccess");
|
||||
mProviderResponseSet = true;
|
||||
updateStatusAndInvokeCallback(Status.COMPLETE);
|
||||
}
|
||||
|
||||
/** Called when the provider response resulted in a failure. */
|
||||
@Override // Callback from the remote provider
|
||||
public void onProviderResponseFailure(int errorCode, Exception exception) {
|
||||
if (exception instanceof ClearCredentialStateException) {
|
||||
mProviderException = (ClearCredentialStateException) exception;
|
||||
}
|
||||
updateStatusAndInvokeCallback(toStatus(errorCode));
|
||||
}
|
||||
|
||||
/** Called when provider service dies. */
|
||||
@Override // Callback from the remote provider
|
||||
public void onProviderServiceDied(RemoteCredentialService service) {
|
||||
if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) {
|
||||
updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
|
||||
} else {
|
||||
Slog.i(TAG, "Component names different in onProviderServiceDied - "
|
||||
+ "this should not happen");
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected ProviderData prepareUiData() {
|
||||
//Not applicable for clearCredential as response is not picked by the user
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUiEntrySelected(String entryType, String entryId,
|
||||
ProviderPendingIntentResponse providerPendingIntentResponse) {
|
||||
//Not applicable for clearCredential as response is not picked by the user
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@ public abstract class ProviderSession<T, R>
|
||||
@Nullable protected Credential mFinalCredentialResponse;
|
||||
@NonNull protected final T mProviderRequest;
|
||||
@Nullable protected R mProviderResponse;
|
||||
@NonNull protected Boolean mProviderResponseSet = false;
|
||||
@Nullable protected Pair<String, CredentialEntry> mUiRemoteEntry;
|
||||
|
||||
|
||||
@@ -84,7 +85,8 @@ public abstract class ProviderSession<T, R>
|
||||
*/
|
||||
public static boolean isCompletionStatus(Status status) {
|
||||
return status == Status.CREDENTIAL_RECEIVED_FROM_INTENT
|
||||
|| status == Status.CREDENTIAL_RECEIVED_FROM_SELECTION;
|
||||
|| status == Status.CREDENTIAL_RECEIVED_FROM_SELECTION
|
||||
|| status == Status.COMPLETE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,7 +132,8 @@ public abstract class ProviderSession<T, R>
|
||||
CREDENTIAL_RECEIVED_FROM_INTENT,
|
||||
PENDING_INTENT_INVOKED,
|
||||
CREDENTIAL_RECEIVED_FROM_SELECTION,
|
||||
SAVE_ENTRIES_RECEIVED, CANCELED
|
||||
SAVE_ENTRIES_RECEIVED, CANCELED,
|
||||
COMPLETE
|
||||
}
|
||||
|
||||
/** Converts exception to a provider session status. */
|
||||
@@ -183,6 +186,11 @@ public abstract class ProviderSession<T, R>
|
||||
return mProviderRequest;
|
||||
}
|
||||
|
||||
/** Returns whether the provider response is set. */
|
||||
protected Boolean isProviderResponseSet() {
|
||||
return mProviderResponse != null || mProviderResponseSet;
|
||||
}
|
||||
|
||||
/** Update the response state stored with the provider session. */
|
||||
@Nullable protected R getProviderResponse() {
|
||||
return mProviderResponse;
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.credentials.ClearCredentialStateException;
|
||||
import android.credentials.CreateCredentialException;
|
||||
import android.credentials.GetCredentialException;
|
||||
import android.os.Handler;
|
||||
@@ -30,10 +31,12 @@ import android.service.credentials.BeginCreateCredentialRequest;
|
||||
import android.service.credentials.BeginCreateCredentialResponse;
|
||||
import android.service.credentials.BeginGetCredentialRequest;
|
||||
import android.service.credentials.BeginGetCredentialResponse;
|
||||
import android.service.credentials.ClearCredentialStateRequest;
|
||||
import android.service.credentials.CredentialProviderErrors;
|
||||
import android.service.credentials.CredentialProviderService;
|
||||
import android.service.credentials.IBeginCreateCredentialCallback;
|
||||
import android.service.credentials.IBeginGetCredentialCallback;
|
||||
import android.service.credentials.IClearCredentialStateCallback;
|
||||
import android.service.credentials.ICredentialProviderService;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Log;
|
||||
@@ -195,6 +198,53 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
|
||||
handleExecutionResponse(result, error, cancellationSink, callback)));
|
||||
}
|
||||
|
||||
/** Main entry point to be called for executing a clearCredentialState call on the remote
|
||||
* provider service.
|
||||
* @param request the request to be sent to the provider
|
||||
* @param callback the callback to be used to send back the provider response to the
|
||||
* {@link ProviderClearSession} class that maintains provider state
|
||||
*/
|
||||
public void onClearCredentialState(@NonNull ClearCredentialStateRequest request,
|
||||
ProviderCallbacks<Void> callback) {
|
||||
Log.i(TAG, "In onClearCredentialState in RemoteCredentialService");
|
||||
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
|
||||
AtomicReference<CompletableFuture<Void>> futureRef = new AtomicReference<>();
|
||||
|
||||
CompletableFuture<Void> connectThenExecute =
|
||||
postAsync(service -> {
|
||||
CompletableFuture<Void> clearCredentialFuture =
|
||||
new CompletableFuture<>();
|
||||
ICancellationSignal cancellationSignal = service.onClearCredentialState(
|
||||
request, new IClearCredentialStateCallback.Stub() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
Log.i(TAG, "In onSuccess onClearCredentialState "
|
||||
+ "in RemoteCredentialService");
|
||||
clearCredentialFuture.complete(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(String errorType, CharSequence message) {
|
||||
Log.i(TAG, "In onFailure in RemoteCredentialService");
|
||||
String errorMsg = message == null ? "" :
|
||||
String.valueOf(message);
|
||||
clearCredentialFuture.completeExceptionally(
|
||||
new ClearCredentialStateException(errorType, errorMsg));
|
||||
}});
|
||||
CompletableFuture<Void> future = futureRef.get();
|
||||
if (future != null && future.isCancelled()) {
|
||||
dispatchCancellationSignal(cancellationSignal);
|
||||
} else {
|
||||
cancellationSink.set(cancellationSignal);
|
||||
}
|
||||
return clearCredentialFuture;
|
||||
}).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
|
||||
|
||||
futureRef.set(connectThenExecute);
|
||||
connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
|
||||
handleExecutionResponse(result, error, cancellationSink, callback)));
|
||||
}
|
||||
|
||||
private <T> void handleExecutionResponse(T result,
|
||||
Throwable error,
|
||||
AtomicReference<ICancellationSignal> cancellationSink,
|
||||
|
||||
@@ -144,7 +144,7 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
|
||||
mProviders.clear();
|
||||
}
|
||||
|
||||
private boolean isAnyProviderPending() {
|
||||
boolean isAnyProviderPending() {
|
||||
for (ProviderSession session : mProviders.values()) {
|
||||
if (ProviderSession.isStatusWaitingForRemoteResponse(session.getStatus())) {
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user