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.content.pm.ApplicationInfo;
25 import android.content.pm.PackageManager;
26 import android.credentials.Credential;
27 import android.credentials.CredentialManager;
28 import android.credentials.CredentialProviderInfo;
29 import android.credentials.selection.ProviderData;
30 import android.credentials.selection.ProviderPendingIntentResponse;
31 import android.os.ICancellationSignal;
32 import android.os.RemoteException;
33 import android.util.Slog;
34 
35 import com.android.server.credentials.metrics.ProviderSessionMetric;
36 
37 import java.util.UUID;
38 
39 /**
40  * Provider session storing the state of provider response and ui entries.
41  *
42  * @param <T> The request to be sent to the provider
43  * @param <R> The response to be expected from the provider
44  */
45 public abstract class ProviderSession<T, R>
46         implements RemoteCredentialService.ProviderCallbacks<R> {
47 
48     private static final String TAG = CredentialManager.TAG;
49 
50     @NonNull
51     protected final Context mContext;
52     @NonNull
53     protected final ComponentName mComponentName;
54     @Nullable
55     protected final CredentialProviderInfo mProviderInfo;
56     @Nullable
57     protected final RemoteCredentialService mRemoteCredentialService;
58     @NonNull
59     protected final int mUserId;
60     @NonNull
61     protected Status mStatus = Status.NOT_STARTED;
62     @Nullable
63     protected final ProviderInternalCallback mCallbacks;
64     @Nullable
65     protected Credential mFinalCredentialResponse;
66     @Nullable
67     protected ICancellationSignal mProviderCancellationSignal;
68     @NonNull
69     protected final T mProviderRequest;
70     @Nullable
71     protected R mProviderResponse;
72     @NonNull
73     protected Boolean mProviderResponseSet = false;
74     @NonNull
75     protected final ProviderSessionMetric mProviderSessionMetric;
76     @NonNull
77     private int mProviderSessionUid;
78 
79     enum CredentialsSource {
80         REMOTE_PROVIDER,
81         REGISTRY,
82         AUTH_ENTRY
83     }
84 
85     /**
86      * Returns true if the given status reflects that the provider state is ready to be shown
87      * on the credMan UI.
88      */
isUiInvokingStatus(Status status)89     public static boolean isUiInvokingStatus(Status status) {
90         return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED
91                 || status == Status.NO_CREDENTIALS_FROM_AUTH_ENTRY;
92     }
93 
94     /**
95      * Returns true if the given status reflects that the provider is waiting for a remote
96      * response.
97      */
isStatusWaitingForRemoteResponse(Status status)98     public static boolean isStatusWaitingForRemoteResponse(Status status) {
99         return status == Status.PENDING;
100     }
101 
102     /**
103      * Returns true if the given status means that the provider session must be terminated.
104      */
isTerminatingStatus(Status status)105     public static boolean isTerminatingStatus(Status status) {
106         return status == Status.CANCELED || status == Status.SERVICE_DEAD;
107     }
108 
109     /**
110      * Returns true if the given status reflects that the provider is done getting the response,
111      * and is ready to return the final credential back to the user.
112      */
isCompletionStatus(Status status)113     public static boolean isCompletionStatus(Status status) {
114         return status == Status.COMPLETE || status == Status.EMPTY_RESPONSE;
115     }
116 
117     /**
118      * Gives access to the objects metric collectors.
119      */
getProviderSessionMetric()120     public ProviderSessionMetric getProviderSessionMetric() {
121         return this.mProviderSessionMetric;
122     }
123 
124     /**
125      * Interface to be implemented by any class that wishes to get a callback when a particular
126      * provider session's status changes. Typically, implemented by the {@link RequestSession}
127      * class.
128      *
129      * @param <V> the type of the final response expected
130      */
131     public interface ProviderInternalCallback<V> {
132         /** Called when status changes. */
onProviderStatusChanged(Status status, ComponentName componentName, CredentialsSource source)133         void onProviderStatusChanged(Status status, ComponentName componentName,
134                 CredentialsSource source);
135 
136         /** Called when the final credential is received through an entry selection. */
onFinalResponseReceived(ComponentName componentName, V response)137         void onFinalResponseReceived(ComponentName componentName, V response);
138 
139         /** Called when an error is received through an entry selection. */
onFinalErrorReceived(ComponentName componentName, String errorType, @Nullable String message)140         void onFinalErrorReceived(ComponentName componentName, String errorType,
141                 @Nullable String message);
142     }
143 
ProviderSession(@onNull Context context, @NonNull T providerRequest, @Nullable ProviderInternalCallback callbacks, @NonNull ComponentName componentName, @NonNull int userId, @Nullable RemoteCredentialService remoteCredentialService)144     protected ProviderSession(@NonNull Context context,
145             @NonNull T providerRequest,
146             @Nullable ProviderInternalCallback callbacks,
147             @NonNull ComponentName componentName,
148             @NonNull int userId,
149             @Nullable RemoteCredentialService remoteCredentialService) {
150         mContext = context;
151         mProviderInfo = null;
152         mProviderRequest = providerRequest;
153         mCallbacks = callbacks;
154         mUserId = userId;
155         mComponentName = componentName;
156         mRemoteCredentialService = remoteCredentialService;
157         mProviderSessionUid = MetricUtilities.getPackageUid(mContext, mComponentName, userId);
158         mProviderSessionMetric = new ProviderSessionMetric(
159                 ((RequestSession) mCallbacks).mRequestSessionMetric.getSessionIdTrackTwo());
160     }
161 
162     /** Provider status at various states of the provider session. */
163     enum Status {
164         NOT_STARTED,
165         PENDING,
166         CREDENTIALS_RECEIVED,
167         SERVICE_DEAD,
168         SAVE_ENTRIES_RECEIVED,
169         CANCELED,
170         EMPTY_RESPONSE,
171         NO_CREDENTIALS_FROM_AUTH_ENTRY,
172         COMPLETE
173     }
174 
generateUniqueId()175     protected static String generateUniqueId() {
176         return UUID.randomUUID().toString();
177     }
178 
getFinalCredentialResponse()179     public Credential getFinalCredentialResponse() {
180         return mFinalCredentialResponse;
181     }
182 
183     /** Propagates cancellation signal to the remote provider service. */
cancelProviderRemoteSession()184     public void cancelProviderRemoteSession() {
185         try {
186             if (mProviderCancellationSignal != null) {
187                 mProviderCancellationSignal.cancel();
188             }
189             setStatus(Status.CANCELED);
190         } catch (RemoteException e) {
191             Slog.e(TAG, "Issue while cancelling provider session: ", e);
192         }
193     }
194 
setStatus(@onNull Status status)195     protected void setStatus(@NonNull Status status) {
196         mStatus = status;
197     }
198 
199     @NonNull
getStatus()200     protected Status getStatus() {
201         return mStatus;
202     }
203 
204     @NonNull
getComponentName()205     protected ComponentName getComponentName() {
206         return mComponentName;
207     }
208 
209     @Nullable
getRemoteCredentialService()210     protected RemoteCredentialService getRemoteCredentialService() {
211         return mRemoteCredentialService;
212     }
213 
214     /** Updates the status . */
updateStatusAndInvokeCallback(@onNull Status status, CredentialsSource source)215     protected void updateStatusAndInvokeCallback(@NonNull Status status,
216             CredentialsSource source) {
217         setStatus(status);
218         boolean isPrimary = mProviderInfo != null && mProviderInfo.isPrimary();
219         mProviderSessionMetric.collectCandidateMetricUpdate(isTerminatingStatus(status)
220                         || isStatusWaitingForRemoteResponse(status),
221                 isCompletionStatus(status) || isUiInvokingStatus(status),
222                 mProviderSessionUid,
223                 /*isAuthEntry*/source == CredentialsSource.AUTH_ENTRY,
224                 /*isPrimary*/isPrimary);
225         mCallbacks.onProviderStatusChanged(status, mComponentName, source);
226     }
227     /** Common method that transfers metrics from the init phase to candidates */
startCandidateMetrics()228     protected void startCandidateMetrics() {
229         mProviderSessionMetric.collectCandidateMetricSetupViaInitialMetric(
230                 ((RequestSession) mCallbacks).mRequestSessionMetric.getInitialPhaseMetric());
231     }
232 
233     /** Get the request to be sent to the provider. */
getProviderRequest()234     protected T getProviderRequest() {
235         return mProviderRequest;
236     }
237 
238     /** Returns whether the provider response is set. */
isProviderResponseSet()239     protected Boolean isProviderResponseSet() {
240         return mProviderResponse != null || mProviderResponseSet;
241     }
242 
invokeCallbackWithError(String errorType, @Nullable String errorMessage)243     protected void invokeCallbackWithError(String errorType, @Nullable String errorMessage) {
244         // TODO: Determine what the error message should be
245         mCallbacks.onFinalErrorReceived(mComponentName, errorType, errorMessage);
246     }
247 
248     /** Update the response state stored with the provider session. */
249     @Nullable
getProviderResponse()250     protected R getProviderResponse() {
251         return mProviderResponse;
252     }
253 
enforceRemoteEntryRestrictions( @ullable ComponentName expectedRemoteEntryProviderService)254     protected boolean enforceRemoteEntryRestrictions(
255             @Nullable ComponentName expectedRemoteEntryProviderService) {
256         // Check if the service is the one set by the OEM. If not silently reject this entry
257         if (!mComponentName.equals(expectedRemoteEntryProviderService)) {
258             Slog.w(TAG, "Remote entry being dropped as it is not from the service "
259                     + "configured by the OEM.");
260             return false;
261         }
262         // Check if the service has the hybrid permission .If not, silently reject this entry.
263         // This check is in addition to the permission check happening in the provider's process.
264         try {
265             ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
266                     mComponentName.getPackageName(),
267                     PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY));
268             if (appInfo != null
269                     && mContext.checkPermission(
270                     Manifest.permission.PROVIDE_REMOTE_CREDENTIALS,
271                     /*pId=*/-1, appInfo.uid) == PackageManager.PERMISSION_GRANTED) {
272                 return true;
273             }
274         } catch (SecurityException | PackageManager.NameNotFoundException e) {
275             Slog.e(TAG, "Error getting info for " + mComponentName.flattenToString(), e);
276             return false;
277         }
278         return false;
279     }
280 
281     /**
282      * Should be overridden to prepare, and stores state for {@link ProviderData} to be
283      * shown on the UI.
284      */
285     @Nullable
prepareUiData()286     protected abstract ProviderData prepareUiData();
287 
288     /** Should be overridden to handle the selected entry from the UI. */
onUiEntrySelected(String entryType, String entryId, ProviderPendingIntentResponse providerPendingIntentResponse)289     protected abstract void onUiEntrySelected(String entryType, String entryId,
290             ProviderPendingIntentResponse providerPendingIntentResponse);
291 
292     /**
293      * Should be overridden to invoke the provider at a defined location. Helpful for
294      * situations such as metric generation.
295      */
invokeSession()296     protected abstract void invokeSession();
297 }
298