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

Commit a2bcfdc7 authored by Omer Ozer's avatar Omer Ozer Committed by Android (Google) Code Review
Browse files

Merge "Add CredentialDescription get impl."

parents ba43b84c d6040afd
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