Merge "Add clear cred implementaion Test: Built locally"

This commit is contained in:
TreeHugger Robot
2022-12-21 05:16:17 +00:00
committed by Android (Google) Code Review
8 changed files with 364 additions and 9 deletions

View File

@@ -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. */

View File

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

View File

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

View File

@@ -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. */

View File

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

View File

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

View File

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

View File

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