1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.credentials;
18 
19 import android.Manifest;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.credentials.CreateCredentialException;
25 import android.credentials.CreateCredentialRequest;
26 import android.credentials.CreateCredentialResponse;
27 import android.credentials.CredentialManager;
28 import android.credentials.CredentialProviderInfo;
29 import android.credentials.ICreateCredentialCallback;
30 import android.credentials.ui.ProviderData;
31 import android.credentials.ui.RequestInfo;
32 import android.os.CancellationSignal;
33 import android.os.RemoteException;
34 import android.service.credentials.CallingAppInfo;
35 import android.service.credentials.PermissionUtils;
36 import android.util.Slog;
37 
38 import com.android.server.credentials.metrics.ProviderStatusForMetrics;
39 
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Set;
43 
44 /**
45  * Central session for a single {@link CredentialManager#createCredential} request.
46  * This class listens to the responses from providers, and the UX app, and updates the
47  * provider(s) state maintained in {@link ProviderCreateSession}.
48  */
49 public final class CreateRequestSession extends RequestSession<CreateCredentialRequest,
50         ICreateCredentialCallback, CreateCredentialResponse>
51         implements ProviderSession.ProviderInternalCallback<CreateCredentialResponse> {
52     private static final String TAG = "CreateRequestSession";
53     private final Set<ComponentName> mPrimaryProviders;
54 
CreateRequestSession(@onNull Context context, RequestSession.SessionLifetime sessionCallback, Object lock, int userId, int callingUid, CreateCredentialRequest request, ICreateCredentialCallback callback, CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders, Set<ComponentName> primaryProviders, CancellationSignal cancellationSignal, long startedTimestamp)55     CreateRequestSession(@NonNull Context context, RequestSession.SessionLifetime sessionCallback,
56             Object lock, int userId, int callingUid,
57             CreateCredentialRequest request,
58             ICreateCredentialCallback callback,
59             CallingAppInfo callingAppInfo,
60             Set<ComponentName> enabledProviders,
61             Set<ComponentName> primaryProviders,
62             CancellationSignal cancellationSignal,
63             long startedTimestamp) {
64         super(context, sessionCallback, lock, userId, callingUid, request, callback,
65                 RequestInfo.TYPE_CREATE,
66                 callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp);
67         mRequestSessionMetric.collectCreateFlowInitialMetricInfo(
68                 /*origin=*/request.getOrigin() != null, request);
69         mPrimaryProviders = primaryProviders;
70     }
71 
72     /**
73      * Creates a new provider session, and adds it to list of providers that are contributing to
74      * this request session.
75      *
76      * @return the provider session that was started
77      */
78     @Override
79     @Nullable
initiateProviderSession(CredentialProviderInfo providerInfo, RemoteCredentialService remoteCredentialService)80     public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
81             RemoteCredentialService remoteCredentialService) {
82         ProviderCreateSession providerCreateSession = ProviderCreateSession
83                 .createNewSession(mContext, mUserId, providerInfo,
84                         this, remoteCredentialService);
85         if (providerCreateSession != null) {
86             Slog.i(TAG, "Provider session created and "
87                     + "being added for: " + providerInfo.getComponentName());
88             mProviders.put(providerCreateSession.getComponentName().flattenToString(),
89                     providerCreateSession);
90         }
91         return providerCreateSession;
92     }
93 
94     @Override
launchUiWithProviderData(ArrayList<ProviderData> providerDataList)95     protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
96         mRequestSessionMetric.collectUiCallStartTime(System.nanoTime());
97         mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.USER_INTERACTION);
98         cancelExistingPendingIntent();
99         try {
100             List<String> flattenedPrimaryProviders = new ArrayList<>();
101             for (ComponentName cn : mPrimaryProviders) {
102                 flattenedPrimaryProviders.add(cn.flattenToString());
103             }
104 
105             mPendingIntent = mCredentialManagerUi.createPendingIntent(
106                     RequestInfo.newCreateRequestInfo(
107                             mRequestId, mClientRequest,
108                             mClientAppInfo.getPackageName(),
109                             PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
110                                     Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
111                             /*defaultProviderId=*/flattenedPrimaryProviders),
112                     providerDataList);
113             mClientCallback.onPendingIntent(mPendingIntent);
114         } catch (RemoteException e) {
115             mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
116             mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED);
117             respondToClientWithErrorAndFinish(
118                     CreateCredentialException.TYPE_UNKNOWN,
119                     "Unable to invoke selector");
120         }
121     }
122 
123     @Override
invokeClientCallbackSuccess(CreateCredentialResponse response)124     protected void invokeClientCallbackSuccess(CreateCredentialResponse response)
125             throws RemoteException {
126         mClientCallback.onResponse(response);
127     }
128 
129     @Override
invokeClientCallbackError(String errorType, String errorMsg)130     protected void invokeClientCallbackError(String errorType, String errorMsg)
131             throws RemoteException {
132         mClientCallback.onError(errorType, errorMsg);
133     }
134 
135     @Override
onFinalResponseReceived(ComponentName componentName, @Nullable CreateCredentialResponse response)136     public void onFinalResponseReceived(ComponentName componentName,
137             @Nullable CreateCredentialResponse response) {
138         Slog.i(TAG, "Final credential received from: " + componentName.flattenToString());
139         mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
140         mRequestSessionMetric.updateMetricsOnResponseReceived(mProviders, componentName,
141                 isPrimaryProviderViaProviderInfo(componentName));
142         if (response != null) {
143             mRequestSessionMetric.collectChosenProviderStatus(
144                     ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
145             respondToClientWithResponseAndFinish(response);
146         } else {
147             mRequestSessionMetric.collectChosenProviderStatus(
148                     ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
149             String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS;
150             mRequestSessionMetric.collectFrameworkException(exception);
151             respondToClientWithErrorAndFinish(exception,
152                     "Invalid response");
153         }
154     }
155 
156     @Override
onFinalErrorReceived(ComponentName componentName, String errorType, String message)157     public void onFinalErrorReceived(ComponentName componentName, String errorType,
158             String message) {
159         respondToClientWithErrorAndFinish(errorType, message);
160     }
161 
162     @Override
onUiCancellation(boolean isUserCancellation)163     public void onUiCancellation(boolean isUserCancellation) {
164         String exception = CreateCredentialException.TYPE_USER_CANCELED;
165         String message = "User cancelled the selector";
166         if (!isUserCancellation) {
167             exception = CreateCredentialException.TYPE_INTERRUPTED;
168             message = "The UI was interrupted - please try again.";
169         }
170         mRequestSessionMetric.collectFrameworkException(exception);
171         respondToClientWithErrorAndFinish(exception, message);
172     }
173 
174     @Override
onUiSelectorInvocationFailure()175     public void onUiSelectorInvocationFailure() {
176         String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS;
177         mRequestSessionMetric.collectFrameworkException(exception);
178         respondToClientWithErrorAndFinish(exception,
179                 "No create options available.");
180     }
181 
182     @Override
onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName, ProviderSession.CredentialsSource source)183     public void onProviderStatusChanged(ProviderSession.Status status,
184             ComponentName componentName, ProviderSession.CredentialsSource source) {
185         Slog.i(TAG, "Provider status changed: " + status + ", and source: " + source);
186         // If all provider responses have been received, we can either need the UI,
187         // or we need to respond with error. The only other case is the entry being
188         // selected after the UI has been invoked which has a separate code path.
189         if (!isAnyProviderPending()) {
190             if (isUiInvocationNeeded()) {
191                 Slog.i(TAG, "Provider status changed - ui invocation is needed");
192                 getProviderDataAndInitiateUi();
193             } else {
194                 String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS;
195                 mRequestSessionMetric.collectFrameworkException(exception);
196                 respondToClientWithErrorAndFinish(exception,
197                         "No create options available.");
198             }
199         }
200     }
201 }
202