Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d6040afd authored by Omer Ozer's avatar Omer Ozer
Browse files

Add CredentialDescription get impl.

Bug: 260629338
CTS-Coverage-Bug: 265212839
API-Coverage-Bug: 265212839
Test: Local Build & Deployment
Change-Id: I44b4fba89cee00b51455d3ddf8696477b98045e6
parent 1ba51e8e
Loading
Loading
Loading
Loading
+68 −19
Original line number Diff line number Diff line
@@ -19,27 +19,50 @@ package com.android.server.credentials;
import android.credentials.CredentialDescription;
import android.credentials.RegisterCredentialDescriptionRequest;
import android.credentials.UnregisterCredentialDescriptionRequest;
import android.service.credentials.CredentialEntry;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;

/** Contains information on what CredentialProvider has what provisioned Credential. */
public class CredentialDescriptionRegistry {
public final class CredentialDescriptionRegistry {

    private static final int MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS = 128;
    private static final int MAX_ALLOWED_ENTRIES_PER_PROVIDER = 16;
    private static SparseArray<CredentialDescriptionRegistry> sCredentialDescriptionSessionPerUser;
    @GuardedBy("sLock")
    private static final SparseArray<CredentialDescriptionRegistry>
            sCredentialDescriptionSessionPerUser;
    private static final ReentrantLock sLock;

    static {
        sCredentialDescriptionSessionPerUser = new SparseArray<>();
        sLock = new ReentrantLock();
    }

    /** Represents the results of a given query into the registry. */
    public static final class FilterResult {
        final String mPackageName;
        final List<CredentialEntry> mCredentialEntries;

        private FilterResult(String packageName,
                List<CredentialEntry> credentialEntries) {
            mPackageName = packageName;
            mCredentialEntries = credentialEntries;
        }
    }

    // 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. */
    @GuardedBy("sLock")
    public static CredentialDescriptionRegistry forUser(int userId) {
        sLock.lock();
        try {
            CredentialDescriptionRegistry session =
                    sCredentialDescriptionSessionPerUser.get(userId, null);

@@ -48,6 +71,20 @@ public class CredentialDescriptionRegistry {
                sCredentialDescriptionSessionPerUser.put(userId, session);
            }
            return session;
        } finally {
            sLock.unlock();
        }
    }

    /** Clears an existing session for a given user identifier. */
    @GuardedBy("sLock")
    public static void clearUserSession(int userId) {
        sLock.lock();
        try {
            sCredentialDescriptionSessionPerUser.remove(userId);
        } finally {
            sLock.unlock();
        }
    }

    private Map<String, Set<CredentialDescription>> mCredentialDescriptions;
@@ -74,7 +111,7 @@ public class CredentialDescriptionRegistry {
            int size = mCredentialDescriptions.get(callingPackageName).size();
            mCredentialDescriptions.get(callingPackageName)
                    .addAll(descriptions);
            mTotalDescriptionCount += size - mCredentialDescriptions.get(callingPackageName).size();
            mTotalDescriptionCount += mCredentialDescriptions.get(callingPackageName).size() - size;
        }

    }
@@ -93,21 +130,33 @@ public class CredentialDescriptionRegistry {
        }
    }

    /** Returns package names of CredentialProviders that can satisfy a given
    /** Returns package names and entries of a CredentialProviders that can satisfy a given
     * {@link CredentialDescription}. */
    public Set<String> filterCredentials(String flatRequestString) {
    public Set<FilterResult> getFilteredResultForProvider(String packageName,
            List<String> flatRequestStrings) {
        Set<FilterResult> result = new HashSet<>();
        Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName);
        for (CredentialDescription containedDescription: currentSet) {
            if (flatRequestStrings.contains(containedDescription.getFlattenedRequestString())) {
                result.add(new FilterResult(packageName, containedDescription
                        .getCredentialEntries()));
            }
        }
        return result;
    }

    /** Returns package names of CredentialProviders that can satisfy a given
     * {@link CredentialDescription}. */
    public Set<String> getMatchingProviders(Set<String> flatRequestString) {
        Set<String> result = new HashSet<>();

        for (String componentName: mCredentialDescriptions.keySet()) {
            Set<CredentialDescription> currentSet = mCredentialDescriptions.get(componentName);
        for (String packageName: mCredentialDescriptions.keySet()) {
            Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName);
            for (CredentialDescription containedDescription : currentSet) {
                if (flatRequestString.equals(containedDescription.getFlattenedRequestString())) {
                    result.add(componentName);
                if (flatRequestString.contains(containedDescription.getFlattenedRequestString())) {
                    result.add(packageName);
                }
            }
        }

        return result;
    }

+75 −30
Original line number Diff line number Diff line
@@ -61,13 +61,11 @@ import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.SecureSettingsServiceNameResolver;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
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.
@@ -236,6 +234,7 @@ 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);
@@ -244,44 +243,38 @@ public final class CredentialManagerService
    @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) {
            GetRequestSession session,
            List<String> requestOptions, Set<String> 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);
                }
        for (String packageName: activeCredentialContainers) {
            providerSessions.add(ProviderRegistryGetSession.createNewSession(
                    mContext,
                    UserHandle.getCallingUserId(),
                    session,
                    packageName,
                    requestOptions));
        }
        });
        return providerSessions;
    }

    @NonNull
    private Set<String> getMatchingProviders(GetCredentialRequest request) {
    private Set<String> getFilteredResultFromRegistry(List<CredentialOption> options) {
        // 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.getCredentialOptions().stream().map(
                        credentialOption -> credentialOption
                options.stream().map(
                        getCredentialOption -> getCredentialOption
                                        .getCredentialRetrievalData()
                                        .getString(CredentialOption
                                                .FLATTENED_REQUEST))
                        .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());
        return registry.getMatchingProviders(requestedCredentialDescriptions);
    }

    @SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
@@ -304,6 +297,13 @@ public final class CredentialManagerService
        return providerSessions;
    }

    @Override
    @GuardedBy("CredentialDescriptionRegistry.sLock")
    public void onUserStopped(@NonNull TargetUser user) {
        super.onUserStopped(user);
        CredentialDescriptionRegistry.clearUserSession(user.getUserIdentifier());
    }

    private CallingAppInfo constructCallingAppInfo(String packageName, int userId) {
        final PackageInfo packageInfo;
        try {
@@ -340,13 +340,57 @@ public final class CredentialManagerService
                            request,
                            constructCallingAppInfo(callingPackage, userId));

            List<ProviderSession> providerSessions;

            if (isCredentialDescriptionApiEnabled()) {
                List<CredentialOption> optionsThatRequireActiveCredentials =
                        request.getCredentialOptions().stream()
                                .filter(getCredentialOption ->
                                        !TextUtils.isEmpty(getCredentialOption
                                                .getCredentialRetrievalData().getString(
                                                CredentialOption
                                                        .FLATTENED_REQUEST, null)))
                                .toList();

                List<CredentialOption> optionsThatDoNotRequireActiveCredentials =
                        request.getCredentialOptions().stream()
                                .filter(getCredentialOption ->
                                        TextUtils.isEmpty(getCredentialOption
                                                .getCredentialRetrievalData().getString(
                                                        CredentialOption
                                                                .FLATTENED_REQUEST, null)))
                                .toList();

                List<ProviderSession> sessionsWithoutRemoteService =
                        initiateProviderSessionsWithActiveContainers(session,
                                optionsThatRequireActiveCredentials
                                        .stream().map(getCredentialOption ->
                                                getCredentialOption.getCredentialRetrievalData()
                                                        .getString(CredentialOption
                                                .FLATTENED_REQUEST))
                                        .collect(Collectors.toList()),
                                getFilteredResultFromRegistry(optionsThatRequireActiveCredentials));

                List<ProviderSession> sessionsWithRemoteService = initiateProviderSessions(
                        session,
                        optionsThatDoNotRequireActiveCredentials.stream()
                                .map(CredentialOption::getType)
                                .collect(Collectors.toList()));

                Set<ProviderSession> all = new LinkedHashSet<>();
                all.addAll(sessionsWithRemoteService);
                all.addAll(sessionsWithoutRemoteService);

                providerSessions = new ArrayList<>(all);
            } else {
                // Initiate all provider sessions
            List<ProviderSession> providerSessions =
                providerSessions =
                        initiateProviderSessions(
                                session,
                                request.getCredentialOptions().stream()
                                        .map(CredentialOption::getType)
                                        .collect(Collectors.toList()));
            }

            if (providerSessions.isEmpty()) {
                try {
@@ -363,6 +407,7 @@ public final class CredentialManagerService

            // Iterate over all provider sessions and invoke the request
            providerSessions.forEach(ProviderSession::invokeSession);

            return cancelTransport;
        }

+3 −3
Original line number Diff line number Diff line
@@ -118,8 +118,8 @@ public final class ProviderClearSession extends ProviderSession<ClearCredentialS

    @Override
    protected void invokeSession() {
        this.mRemoteCredentialService.onClearCredentialState(
                this.getProviderRequest(),
                /*callback=*/this);
        if (mRemoteCredentialService != null) {
            mRemoteCredentialService.onClearCredentialState(mProviderRequest, this);
        }
    }
}
+5 −3
Original line number Diff line number Diff line
@@ -51,6 +51,8 @@ public final class ProviderCreateSession extends ProviderSession<

    // Key to be used as an entry key for a save entry
    private static final String SAVE_ENTRY_KEY = "save_entry_key";
    // Key to be used as an entry key for a remote entry
    private static final String REMOTE_ENTRY_KEY = "remote_entry_key";

    @NonNull
    private final Map<String, CreateEntry> mUiSaveEntries = new HashMap<>();
@@ -199,9 +201,9 @@ public final class ProviderCreateSession extends ProviderSession<

    @Override
    protected void invokeSession() {
        this.mRemoteCredentialService.onCreateCredential(
                this.getProviderRequest(),
                /*callback=*/this);
        if (mRemoteCredentialService != null) {
            mRemoteCredentialService.onCreateCredential(mProviderRequest, this);
        }
    }

    private List<Entry> prepareUiSaveEntries(@NonNull List<CreateEntry> saveEntries) {
+45 −45
Original line number Diff line number Diff line
@@ -59,14 +59,14 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
        implements
        RemoteCredentialService.ProviderCallbacks<BeginGetCredentialResponse> {
    private static final String TAG = "ProviderGetSession";

    // Key to be used as an entry key for a credential entry
    private static final String CREDENTIAL_ENTRY_KEY = "credential_key";

    // Key to be used as the entry key for an action entry
    private static final String ACTION_ENTRY_KEY = "action_key";
    // Key to be used as the entry key for the authentication entry
    private static final String AUTHENTICATION_ACTION_ENTRY_KEY = "authentication_action_key";
    // Key to be used as an entry key for a remote entry
    private static final String REMOTE_ENTRY_KEY = "remote_entry_key";
    // Key to be used as an entry key for a credential entry
    private static final String CREDENTIAL_ENTRY_KEY = "credential_key";

    @NonNull
    private final Map<String, CredentialEntry> mUiCredentialEntries = new HashMap<>();
@@ -101,23 +101,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
        return null;
    }

    private static BeginGetCredentialRequest constructQueryPhaseRequest(
            android.credentials.GetCredentialRequest filteredRequest,
            CallingAppInfo callingAppInfo
    ) {
        return new BeginGetCredentialRequest.Builder(callingAppInfo)
                .setBeginGetCredentialOptions(
                        filteredRequest.getCredentialOptions().stream().map(
                                option -> {
                                    return new BeginGetCredentialOption(
                                            option.getType(),
                                            option.getCandidateQueryData());
                                }).collect(Collectors.toList()))
                .build();
    }

    @Nullable
    private static android.credentials.GetCredentialRequest filterOptions(
    protected static android.credentials.GetCredentialRequest filterOptions(
            List<String> providerCapabilities,
            android.credentials.GetCredentialRequest clientRequest
    ) {
@@ -142,6 +127,21 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
        return null;
    }

    private static BeginGetCredentialRequest constructQueryPhaseRequest(
            android.credentials.GetCredentialRequest filteredRequest,
            CallingAppInfo callingAppInfo
    ) {
        return new BeginGetCredentialRequest.Builder(callingAppInfo)
                .setBeginGetCredentialOptions(
                        filteredRequest.getCredentialOptions().stream().map(
                                option -> {
                                    return new BeginGetCredentialOption(
                                            option.getType(),
                                            option.getCandidateQueryData());
                                }).collect(Collectors.toList()))
                .build();
    }

    public ProviderGetSession(Context context,
            CredentialProviderInfo info,
            ProviderInternalCallback<GetCredentialResponse> callbacks,
@@ -232,9 +232,9 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential

    @Override
    protected void invokeSession() {
        this.mRemoteCredentialService.onBeginGetCredential(
                        this.getProviderRequest(),
                        /*callback=*/this);
        if (mRemoteCredentialService != null) {
            mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this);
        }
    }

    @Override // Call from request session to data to be shown on the UI
@@ -379,6 +379,28 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
        invokeCallbackOnInternalInvalidState();
    }

    @Nullable
    protected GetCredentialException maybeGetPendingIntentException(
            ProviderPendingIntentResponse pendingIntentResponse) {
        if (pendingIntentResponse == null) {
            Log.i(TAG, "pendingIntentResponse is null");
            return null;
        }
        if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
            GetCredentialException exception = PendingIntentResultHandler
                    .extractGetCredentialException(pendingIntentResponse.getResultData());
            if (exception != null) {
                Log.i(TAG, "Pending intent contains provider exception");
                return exception;
            }
        } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
            return new GetCredentialException(GetCredentialException.TYPE_USER_CANCELED);
        } else {
            return new GetCredentialException(GetCredentialException.TYPE_NO_CREDENTIAL);
        }
        return null;
    }

    private void onAuthenticationEntrySelected(
            @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
        //TODO: Other provider intent statuses
@@ -431,28 +453,6 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
        updateStatusAndInvokeCallback(Status.NO_CREDENTIALS);
    }

    @Nullable
    private GetCredentialException maybeGetPendingIntentException(
            ProviderPendingIntentResponse pendingIntentResponse) {
        if (pendingIntentResponse == null) {
            Log.i(TAG, "pendingIntentResponse is null");
            return null;
        }
        if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
            GetCredentialException exception = PendingIntentResultHandler
                    .extractGetCredentialException(pendingIntentResponse.getResultData());
            if (exception != null) {
                Log.i(TAG, "Pending intent contains provider exception");
                return exception;
            }
        } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
            return new GetCredentialException(GetCredentialException.TYPE_USER_CANCELED);
        } else {
            return new GetCredentialException(GetCredentialException.TYPE_NO_CREDENTIAL);
        }
        return null;
    }

    /**
     * When an invalid state occurs, e.g. entry mismatch or no response from provider,
     * we send back a TYPE_UNKNOWN error as to the developer.
Loading