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 android.service.credentials;
18 
19 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
20 
21 import android.annotation.CallSuper;
22 import android.annotation.NonNull;
23 import android.annotation.SdkConstant;
24 import android.app.PendingIntent;
25 import android.app.Service;
26 import android.content.Intent;
27 import android.credentials.ClearCredentialStateException;
28 import android.credentials.CreateCredentialException;
29 import android.credentials.GetCredentialException;
30 import android.os.CancellationSignal;
31 import android.os.Handler;
32 import android.os.IBinder;
33 import android.os.ICancellationSignal;
34 import android.os.Looper;
35 import android.os.OutcomeReceiver;
36 import android.os.RemoteException;
37 import android.util.Slog;
38 
39 import java.util.Objects;
40 
41 /**
42  * Service to be extended by credential providers, in order to return user credentials
43  * to the framework.
44  */
45 public abstract class CredentialProviderService extends Service {
46     /**
47      * Intent extra: The {@link android.credentials.CreateCredentialRequest} attached with
48      * the {@code pendingIntent} that is invoked when the user selects a {@link CreateEntry}
49      * returned as part of the {@link BeginCreateCredentialResponse}
50      *
51      * <p>
52      * Type: {@link android.service.credentials.CreateCredentialRequest}
53      */
54     public static final String EXTRA_CREATE_CREDENTIAL_REQUEST =
55             "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST";
56 
57     /**
58      * Intent extra: The {@link GetCredentialRequest} attached with
59      * the {@code pendingIntent} that is invoked when the user selects a {@link CredentialEntry}
60      * returned as part of the {@link BeginGetCredentialResponse}
61      *
62      * <p>
63      * Type: {@link GetCredentialRequest}
64      */
65     public static final String EXTRA_GET_CREDENTIAL_REQUEST =
66             "android.service.credentials.extra.GET_CREDENTIAL_REQUEST";
67 
68     /**
69      * Intent extra: The result of a create flow operation, to be set on finish of the
70      * {@link android.app.Activity} invoked through the {@code pendingIntent} set on
71      * a {@link CreateEntry}.
72      *
73      * <p>
74      * Type: {@link android.credentials.CreateCredentialResponse}
75      */
76     public static final String EXTRA_CREATE_CREDENTIAL_RESPONSE =
77             "android.service.credentials.extra.CREATE_CREDENTIAL_RESPONSE";
78 
79     /**
80      * Intent extra: The result of a get credential flow operation, to be set on finish of the
81      * {@link android.app.Activity} invoked through the {@code pendingIntent} set on
82      * a {@link CredentialEntry}.
83      *
84      * <p>
85      * Type: {@link android.credentials.GetCredentialResponse}
86      */
87     public static final String EXTRA_GET_CREDENTIAL_RESPONSE =
88             "android.service.credentials.extra.GET_CREDENTIAL_RESPONSE";
89 
90     /**
91      * Intent extra: The result of an authentication flow, to be set on finish of the
92      * {@link android.app.Activity} invoked through the {@link android.app.PendingIntent} set on
93      * an authentication {@link Action}, as part of the original
94      * {@link BeginGetCredentialResponse}. This result should contain the actual content,
95      * including credential entries and action entries, to be shown on the selector.
96      *
97      * <p>
98      * Type: {@link BeginGetCredentialResponse}
99      */
100     public static final String EXTRA_BEGIN_GET_CREDENTIAL_RESPONSE =
101             "android.service.credentials.extra.BEGIN_GET_CREDENTIAL_RESPONSE";
102 
103     /**
104      * Intent extra: The failure exception set at the final stage of a get flow.
105      * This exception is set at the finishing result of the {@link android.app.Activity}
106      * invoked by the {@link PendingIntent} , when a user selects the {@link CredentialEntry}
107      * that contained the {@link PendingIntent} in question.
108      *
109      * <p>The result must be set through {@link android.app.Activity#setResult} as an intent extra
110      *
111      * <p>
112      * Type: {@link android.credentials.GetCredentialException}
113      */
114     public static final String EXTRA_GET_CREDENTIAL_EXCEPTION =
115             "android.service.credentials.extra.GET_CREDENTIAL_EXCEPTION";
116 
117     /**
118      * Intent extra: The failure exception set at the final stage of a create flow.
119      * This exception is set at the finishing result of the {@link android.app.Activity}
120      * invoked by the {@link PendingIntent} , when a user selects the {@link CreateEntry}
121      * that contained the {@link PendingIntent} in question.
122      *
123      * <p>
124      * Type: {@link android.credentials.CreateCredentialException}
125      */
126     public static final String EXTRA_CREATE_CREDENTIAL_EXCEPTION =
127             "android.service.credentials.extra.CREATE_CREDENTIAL_EXCEPTION";
128 
129     /**
130      * Intent extra: The {@link BeginGetCredentialRequest} attached with
131      * the {@code pendingIntent} that is invoked when the user selects an
132      * authentication entry (intending to unlock the provider app) on the UI.
133      *
134      * <p>When a provider app receives a {@link BeginGetCredentialRequest} through the
135      * {@link CredentialProviderService#onBeginGetCredential} call, it can construct the
136      * {@link BeginGetCredentialResponse} with either an authentication {@link Action} (if the app
137      * is locked), or a {@link BeginGetCredentialResponse} (if the app is unlocked). In the former
138      * case, i.e. the app is locked, user will be shown the authentication action. When selected,
139      * the underlying {@link PendingIntent} will be invoked which will lead the user to provider's
140      * unlock activity. This pending intent will also contain the original
141      * {@link BeginGetCredentialRequest} to be retrieved and processed after the unlock
142      * flow is complete.
143      *
144      * <p>After the app is unlocked, the {@link BeginGetCredentialResponse} must be constructed
145      * using a {@link BeginGetCredentialResponse}, which must be set on an {@link Intent} as an
146      * intent extra against CredentialProviderService#EXTRA_CREDENTIALS_RESPONSE_CONTENT}.
147      * This intent should then be set as a result through {@link android.app.Activity#setResult}
148      * before finishing the activity.
149      *
150      * <p>
151      * Type: {@link BeginGetCredentialRequest}
152      */
153     public static final String EXTRA_BEGIN_GET_CREDENTIAL_REQUEST =
154             "android.service.credentials.extra.BEGIN_GET_CREDENTIAL_REQUEST";
155 
156     /**
157      * The key to autofillId associated with the requested credential option and the corresponding
158      * credential entry. The associated autofillId will be contained inside the candidate query
159      * bundle of {@link android.credentials.CredentialOption} if requested through the
160      * {@link com.android.credentialmanager.autofill.CredentialAutofillService}. The resulting
161      * credential entry will  contain the autofillId inside its framework extras intent.
162      *
163      * @hide
164      */
165     public static final String EXTRA_AUTOFILL_ID =
166             "android.service.credentials.extra.AUTOFILL_ID";
167 
168     private static final String TAG = "CredProviderService";
169 
170      /**
171       * Name under which a Credential Provider service component publishes information
172       * about itself.  This meta-data must reference an XML resource containing
173       * an
174       * <code>&lt;{@link android.R.styleable#CredentialProvider credential-provider}&gt;</code>
175       * tag.
176       *
177       * For example (AndroidManifest.xml):
178       * <code>
179       * <meta-data
180       *         android:name="android.credentials.provider"
181       *          android:resource="@xml/provider"/>
182       * </code>
183       *
184       * For example (xml/provider.xml):
185       * <code>
186       * <credential-provider xmlns:android="http://schemas.android.com/apk/res/android"
187       *       android:settingsSubtitle="@string/providerSubtitle">
188       *      <capabilities>
189       *          <capability>@string/passwords</capability>
190       *          <capability>@string/passkeys</capability>
191       *      </capabilities>
192       *      <capability name="android.credentials.TYPE_PASSWORD_CREDENTIAL" />
193       *      <capability name="android.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" />
194       *  </credential-provider>
195       * </code>
196       */
197     public static final String SERVICE_META_DATA = "android.credentials.provider";
198 
199     /** @hide */
200     public static final String TEST_SYSTEM_PROVIDER_META_DATA_KEY =
201             "android.credentials.testsystemprovider";
202 
203     private Handler mHandler;
204 
205     /**
206      * The {@link Intent} that must be declared as handled by the service. The service must also
207      * require the {android.Manifest.permission#BIND_CREDENTIAL_PROVIDER_SERVICE} permission
208      * so that only the system can bind to it.
209      */
210     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
211     public static final String SERVICE_INTERFACE =
212             "android.service.credentials.CredentialProviderService";
213 
214     /**
215      * The {@link Intent} that must be declared as handled by a system credential provider
216      * service.
217      *
218      * <p>The service must also require the
219      * {android.Manifest.permission#BIND_CREDENTIAL_PROVIDER_SERVICE} permission
220      * so that only the system can bind to it.
221      *
222      * @hide
223      */
224     @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
225     public static final String SYSTEM_SERVICE_INTERFACE =
226             "android.service.credentials.system.CredentialProviderService";
227 
228     @CallSuper
229     @Override
onCreate()230     public void onCreate() {
231         super.onCreate();
232         mHandler = new Handler(Looper.getMainLooper(), null, true);
233     }
234 
235     @Override
onBind(@onNull Intent intent)236     @NonNull public final IBinder onBind(@NonNull Intent intent) {
237         if (SERVICE_INTERFACE.equals(intent.getAction())) {
238             return mInterface.asBinder();
239         }
240         Slog.w(TAG, "Failed to bind with intent: " + intent);
241         return null;
242     }
243 
244     private final ICredentialProviderService mInterface = new ICredentialProviderService.Stub() {
245         @Override
246         public void onBeginGetCredential(BeginGetCredentialRequest request,
247                 IBeginGetCredentialCallback callback) {
248             Objects.requireNonNull(request);
249             Objects.requireNonNull(callback);
250 
251             ICancellationSignal transport = CancellationSignal.createTransport();
252             try {
253                 callback.onCancellable(transport);
254             } catch (RemoteException e) {
255                 e.rethrowFromSystemServer();
256             }
257 
258             mHandler.sendMessage(obtainMessage(
259                     CredentialProviderService::onBeginGetCredential,
260                     CredentialProviderService.this, request,
261                     CancellationSignal.fromTransport(transport),
262                     new OutcomeReceiver<BeginGetCredentialResponse,
263                             GetCredentialException>() {
264                         @Override
265                         public void onResult(BeginGetCredentialResponse result) {
266                             try {
267                                 callback.onSuccess(result);
268                             } catch (RemoteException e) {
269                                 e.rethrowFromSystemServer();
270                             }
271                         }
272                         @Override
273                         public void onError(GetCredentialException e) {
274                             try {
275                                 callback.onFailure(e.getType(), e.getMessage());
276                             } catch (RemoteException ex) {
277                                 ex.rethrowFromSystemServer();
278                             }
279                         }
280                     }
281             ));
282         }
283 
284         @Override
285         public void onBeginCreateCredential(BeginCreateCredentialRequest request,
286                 IBeginCreateCredentialCallback callback) {
287             Objects.requireNonNull(request);
288             Objects.requireNonNull(callback);
289 
290             ICancellationSignal transport = CancellationSignal.createTransport();
291             try {
292                 callback.onCancellable(transport);
293             } catch (RemoteException e) {
294                 e.rethrowFromSystemServer();
295             }
296 
297             mHandler.sendMessage(obtainMessage(
298                     CredentialProviderService::onBeginCreateCredential,
299                     CredentialProviderService.this, request,
300                     CancellationSignal.fromTransport(transport),
301                     new OutcomeReceiver<
302                             BeginCreateCredentialResponse, CreateCredentialException>() {
303                         @Override
304                         public void onResult(BeginCreateCredentialResponse result) {
305                             try {
306                                 callback.onSuccess(result);
307                             } catch (RemoteException e) {
308                                 e.rethrowFromSystemServer();
309                             }
310                         }
311                         @Override
312                         public void onError(CreateCredentialException e) {
313                             try {
314                                 callback.onFailure(e.getType(), e.getMessage());
315                             } catch (RemoteException ex) {
316                                 ex.rethrowFromSystemServer();
317                             }
318                         }
319                     }
320             ));
321         }
322 
323         @Override
324         public void onClearCredentialState(ClearCredentialStateRequest request,
325                 IClearCredentialStateCallback callback) {
326             Objects.requireNonNull(request);
327             Objects.requireNonNull(callback);
328 
329             ICancellationSignal transport = CancellationSignal.createTransport();
330             try {
331                 callback.onCancellable(transport);
332             } catch (RemoteException e) {
333                 e.rethrowFromSystemServer();
334             }
335 
336             mHandler.sendMessage(obtainMessage(
337                     CredentialProviderService::onClearCredentialState,
338                     CredentialProviderService.this, request,
339                     CancellationSignal.fromTransport(transport),
340                     new OutcomeReceiver<Void, ClearCredentialStateException>() {
341                         @Override
342                         public void onResult(Void result) {
343                             try {
344                                 callback.onSuccess();
345                             } catch (RemoteException e) {
346                                 e.rethrowFromSystemServer();
347                             }
348                         }
349                         @Override
350                         public void onError(ClearCredentialStateException e) {
351                             try {
352                                 callback.onFailure(e.getType(), e.getMessage());
353                             } catch (RemoteException ex) {
354                                 ex.rethrowFromSystemServer();
355                             }
356                         }
357                     }
358             ));
359         }
360     };
361 
362     /**
363      * Called by the android system to retrieve user credentials from the connected provider
364      * service.
365      *
366      * <p>This API denotes a query stage request for getting user's credentials from a given
367      * credential provider. The request contains a list of
368      * {@link BeginGetCredentialOption} that have parameters to be used for
369      * populating candidate credentials, as a list of {@link CredentialEntry} to be set
370      * on the {@link BeginGetCredentialResponse}. This list is then shown to the user on a
371      * selector.
372      *
373      * <p>If a {@link PendingIntent} is set on a {@link CredentialEntry}, and the user selects that
374      * entry, a {@link GetCredentialRequest} with all parameters needed to get the actual
375      * {@link android.credentials.Credential} will be sent as part of the {@link Intent} fired
376      * through the {@link PendingIntent}.
377      * @param request the request for the provider to handle
378      * @param cancellationSignal signal for providers to listen to any cancellation requests from
379      *                           the android system
380      * @param callback object used to relay the response of the credentials request
381      */
onBeginGetCredential(@onNull BeginGetCredentialRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull OutcomeReceiver< BeginGetCredentialResponse, GetCredentialException> callback)382     public abstract void onBeginGetCredential(@NonNull BeginGetCredentialRequest request,
383             @NonNull CancellationSignal cancellationSignal,
384             @NonNull OutcomeReceiver<
385                     BeginGetCredentialResponse, GetCredentialException> callback);
386 
387     /**
388      * Called by the android system to create a credential.
389      * @param request The credential creation request for the provider to handle.
390      * @param cancellationSignal Signal for providers to listen to any cancellation requests from
391      *                           the android system.
392      * @param callback Object used to relay the response of the credential creation request.
393      */
onBeginCreateCredential(@onNull BeginCreateCredentialRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull OutcomeReceiver<BeginCreateCredentialResponse, CreateCredentialException> callback)394     public abstract void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request,
395             @NonNull CancellationSignal cancellationSignal,
396             @NonNull OutcomeReceiver<BeginCreateCredentialResponse,
397                     CreateCredentialException> callback);
398 
399     /**
400      * Called by the android system to clear the credential state.
401      *
402      * This api isinvoked by developers after users sign out of an app, with an intention to
403      * clear any stored credential session that providers have retained.
404      *
405      * As a provider, you must clear any credential state, if maintained. For e.g. a provider may
406      * have stored an active credential session that is used to limit or rank sign-in options for
407      * future credential retrieval flows. When a user signs out of the app, such state should be
408      * cleared and an exhaustive list of credentials must be presented to the user on subsequent
409      * credential retrieval flows.
410      *
411      * @param request The clear credential request for the provider to handle.
412      * @param cancellationSignal Signal for providers to listen to any cancellation requests from
413      *                           the android system.
414      * @param callback Object used to relay the result of the request.
415      */
onClearCredentialState(@onNull ClearCredentialStateRequest request, @NonNull CancellationSignal cancellationSignal, @NonNull OutcomeReceiver<Void, ClearCredentialStateException> callback)416     public abstract void onClearCredentialState(@NonNull ClearCredentialStateRequest request,
417             @NonNull CancellationSignal cancellationSignal,
418             @NonNull OutcomeReceiver<Void,
419                     ClearCredentialStateException> callback);
420 }
421