1 /*
2  * Copyright 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.credentials;
18 
19 import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS;
20 
21 import static java.util.Objects.requireNonNull;
22 
23 import android.annotation.NonNull;
24 import android.annotation.SuppressLint;
25 import android.content.ComponentName;
26 import android.os.Bundle;
27 import android.os.Parcel;
28 import android.os.Parcelable;
29 import android.util.ArraySet;
30 
31 import androidx.annotation.RequiresPermission;
32 
33 import com.android.internal.util.AnnotationValidations;
34 import com.android.internal.util.Preconditions;
35 
36 import java.util.Set;
37 
38 /**
39  * Information about a specific type of credential to be requested during a {@link
40  * CredentialManager#getCredential} operation.
41  */
42 public final class CredentialOption implements Parcelable {
43 
44     /**
45      * Bundle key to the list of elements keys supported/requested. Framework will use this key
46      * to determine which types of Credentials will utilize Credential Registry when filtering
47      * Credential Providers to ping.
48      */
49     public static final String SUPPORTED_ELEMENT_KEYS = "android.credentials"
50             + ".GetCredentialOption.SUPPORTED_ELEMENT_KEYS";
51 
52     /**
53      * The requested credential type.
54      */
55     @NonNull
56     private final String mType;
57 
58     /**
59      * The full request data.
60      */
61     @NonNull
62     private final Bundle mCredentialRetrievalData;
63 
64     /**
65      * The partial request data that will be sent to the provider during the initial credential
66      * candidate query stage.
67      */
68     @NonNull
69     private final Bundle mCandidateQueryData;
70 
71     /**
72      * Determines whether the request must only be fulfilled by a system provider.
73      */
74     private final boolean mIsSystemProviderRequired;
75 
76     /**
77      * A list of {@link ComponentName}s corresponding to the providers that this option must be
78      * queried against.
79      */
80     @NonNull
81     private final ArraySet<ComponentName> mAllowedProviders;
82 
83 
84     /**
85      * Returns the requested credential type.
86      */
87     @NonNull
getType()88     public String getType() {
89         return mType;
90     }
91 
92     /**
93      * Returns the full request data.
94      */
95     @NonNull
getCredentialRetrievalData()96     public Bundle getCredentialRetrievalData() {
97         return mCredentialRetrievalData;
98     }
99 
100     /**
101      * Returns the partial request data that will be sent to the provider during the initial
102      * credential candidate query stage.
103      *
104      * For security reason, a provider will receive the request data in two stages. First it gets
105      * this partial request that do not contain sensitive user information; it uses this
106      * information to provide credential candidates that the [@code CredentialManager] will show to
107      * the user. Next, the full request data, {@link #getCredentialRetrievalData()}, will be sent to
108      * a provider only if the user further grants the consent by choosing a candidate from the
109      * provider.
110      */
111     @NonNull
getCandidateQueryData()112     public Bundle getCandidateQueryData() {
113         return mCandidateQueryData;
114     }
115 
116     /**
117      * Returns true if the request must only be fulfilled by a system provider, and false
118      * otherwise.
119      */
isSystemProviderRequired()120     public boolean isSystemProviderRequired() {
121         return mIsSystemProviderRequired;
122     }
123 
124     /**
125      * Returns the set of {@link ComponentName} corresponding to providers that must receive
126      * this option.
127      */
128     @NonNull
getAllowedProviders()129     public Set<ComponentName> getAllowedProviders() {
130         return mAllowedProviders;
131     }
132 
133     @Override
writeToParcel(@onNull Parcel dest, int flags)134     public void writeToParcel(@NonNull Parcel dest, int flags) {
135         dest.writeString8(mType);
136         dest.writeBundle(mCredentialRetrievalData);
137         dest.writeBundle(mCandidateQueryData);
138         dest.writeBoolean(mIsSystemProviderRequired);
139         dest.writeArraySet(mAllowedProviders);
140     }
141 
142     @Override
describeContents()143     public int describeContents() {
144         return 0;
145     }
146 
147     @Override
toString()148     public String toString() {
149         return "CredentialOption {"
150                 + "type=" + mType
151                 + ", requestData=" + mCredentialRetrievalData
152                 + ", candidateQueryData=" + mCandidateQueryData
153                 + ", isSystemProviderRequired=" + mIsSystemProviderRequired
154                 + ", allowedProviders=" + mAllowedProviders
155                 + "}";
156     }
157 
158     /**
159      * Constructs a {@link CredentialOption}.
160      *
161      * @param type                     the requested credential type
162      * @param credentialRetrievalData  the request data
163      * @param candidateQueryData       the partial request data that will be sent to the provider
164      *                                 during the initial credential candidate query stage
165      * @param isSystemProviderRequired whether the request must only be fulfilled by a system
166      *                                 provider
167      * @throws IllegalArgumentException If type is empty.
168      */
CredentialOption( @onNull String type, @NonNull Bundle credentialRetrievalData, @NonNull Bundle candidateQueryData, boolean isSystemProviderRequired, @NonNull ArraySet<ComponentName> allowedProviders)169     private CredentialOption(
170             @NonNull String type,
171             @NonNull Bundle credentialRetrievalData,
172             @NonNull Bundle candidateQueryData,
173             boolean isSystemProviderRequired,
174             @NonNull ArraySet<ComponentName> allowedProviders) {
175         mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
176         mCredentialRetrievalData = requireNonNull(credentialRetrievalData,
177                 "requestData must not be null");
178         mCandidateQueryData = requireNonNull(candidateQueryData,
179                 "candidateQueryData must not be null");
180         mIsSystemProviderRequired = isSystemProviderRequired;
181         mAllowedProviders = requireNonNull(allowedProviders, "providerFilterSer must"
182                 + "not be empty");
183     }
184 
185     /**
186      * Constructs a {@link CredentialOption}.
187      *
188      * @param type                     the requested credential type
189      * @param credentialRetrievalData  the request data
190      * @param candidateQueryData       the partial request data that will be sent to the provider
191      *                                 during the initial credential candidate query stage
192      * @param isSystemProviderRequired whether the request must only be fulfilled by a system
193      *                                 provider
194      * @throws IllegalArgumentException If type is empty, or null.
195      * @throws NullPointerException If {@code credentialRetrievalData}, or
196      * {@code candidateQueryData} is null.
197      *
198      * @hide
199      */
CredentialOption( @onNull String type, @NonNull Bundle credentialRetrievalData, @NonNull Bundle candidateQueryData, boolean isSystemProviderRequired)200     public CredentialOption(
201             @NonNull String type,
202             @NonNull Bundle credentialRetrievalData,
203             @NonNull Bundle candidateQueryData,
204             boolean isSystemProviderRequired) {
205         this(
206                 type,
207                 credentialRetrievalData,
208                 candidateQueryData,
209                 isSystemProviderRequired,
210                 new ArraySet<>()
211         );
212     }
213 
CredentialOption(@onNull Parcel in)214     private CredentialOption(@NonNull Parcel in) {
215         String type = in.readString8();
216         Bundle data = in.readBundle();
217         Bundle candidateQueryData = in.readBundle();
218         boolean isSystemProviderRequired = in.readBoolean();
219 
220         mType = type;
221         AnnotationValidations.validate(NonNull.class, null, mType);
222         mCredentialRetrievalData = data;
223         AnnotationValidations.validate(NonNull.class, null, mCredentialRetrievalData);
224         mCandidateQueryData = candidateQueryData;
225         AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
226         mIsSystemProviderRequired = isSystemProviderRequired;
227         mAllowedProviders = (ArraySet<ComponentName>) in.readArraySet(null);
228         AnnotationValidations.validate(NonNull.class, null, mAllowedProviders);
229     }
230 
231     @NonNull
232     public static final Parcelable.Creator<CredentialOption> CREATOR = new Parcelable.Creator<>() {
233         @Override
234         public CredentialOption[] newArray(int size) {
235             return new CredentialOption[size];
236         }
237 
238         @Override
239         public CredentialOption createFromParcel(@NonNull Parcel in) {
240             return new CredentialOption(in);
241         }
242     };
243 
244     /** A builder for {@link CredentialOption}. */
245     public static final class Builder {
246 
247         @NonNull
248         private String mType;
249 
250         @NonNull
251         private Bundle mCredentialRetrievalData;
252 
253         @NonNull
254         private Bundle mCandidateQueryData;
255 
256         private boolean mIsSystemProviderRequired = false;
257 
258         @NonNull
259         private ArraySet<ComponentName> mAllowedProviders = new ArraySet<>();
260 
261         /**
262          * @param type                    the type of the credential option
263          * @param credentialRetrievalData the full request data
264          * @param candidateQueryData      the partial request data that will be sent to the provider
265          *                                during the initial credential candidate query stage.
266          * @throws IllegalArgumentException If {@code type} is null, or empty
267          * @throws NullPointerException     If {@code credentialRetrievalData}, or
268          *                                  {@code candidateQueryData} is null
269          */
Builder(@onNull String type, @NonNull Bundle credentialRetrievalData, @NonNull Bundle candidateQueryData)270         public Builder(@NonNull String type, @NonNull Bundle credentialRetrievalData,
271                 @NonNull Bundle candidateQueryData) {
272             mType = Preconditions.checkStringNotEmpty(type, "type must not be "
273                     + "null, or empty");
274             mCredentialRetrievalData = requireNonNull(credentialRetrievalData,
275                     "credentialRetrievalData must not be null");
276             mCandidateQueryData = requireNonNull(candidateQueryData,
277                     "candidateQueryData must not be null");
278         }
279 
280         /**
281          * Sets a true/false value corresponding to whether this option must be serviced by
282          * system credentials providers only.
283          */
284         @SuppressLint("MissingGetterMatchingBuilder")
285         @NonNull
setIsSystemProviderRequired(boolean isSystemProviderRequired)286         public Builder setIsSystemProviderRequired(boolean isSystemProviderRequired) {
287             mIsSystemProviderRequired = isSystemProviderRequired;
288             return this;
289         }
290 
291         /**
292          * Adds a provider {@link ComponentName} to be queried while gathering credentials from
293          * credential providers on the device.
294          *
295          * If no candidate providers are specified, all user configured and system credential
296          * providers will be queried in the candidate query phase.
297          *
298          * If an invalid component name is provided, or a service corresponding to the
299          * component name does not exist on the device, that component name is ignored.
300          * If all component names are invalid, or not present on the device, no providers
301          * are queried and no credentials are retrieved.
302          *
303          * @throws NullPointerException If {@code allowedProvider} is null
304          */
305         @RequiresPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)
306         @NonNull
addAllowedProvider(@onNull ComponentName allowedProvider)307         public Builder addAllowedProvider(@NonNull ComponentName allowedProvider) {
308             mAllowedProviders.add(requireNonNull(allowedProvider,
309                     "allowedProvider must not be null"));
310             return this;
311         }
312 
313         /**
314          * Sets a set of provider {@link ComponentName} to be queried while gathering credentials
315          * from credential providers on the device.
316          *
317          * If no candidate providers are specified, all user configured and system credential
318          * providers will be queried in the candidate query phase.
319          *
320          * If an invalid component name is provided, or a service corresponding to the
321          * component name does not exist on the device, that component name is ignored.
322          * If all component names are invalid, or not present on the device, no providers
323          * are queried and no credentials are retrieved.
324          *
325          * @throws NullPointerException If {@code allowedProviders} is null, or any of its
326          * elements are null.
327          */
328         @RequiresPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)
329         @NonNull
setAllowedProviders(@onNull Set<ComponentName> allowedProviders)330         public Builder setAllowedProviders(@NonNull Set<ComponentName> allowedProviders) {
331             Preconditions.checkCollectionElementsNotNull(
332                     allowedProviders,
333                     /*valueName=*/ "allowedProviders");
334             mAllowedProviders = new ArraySet<>(allowedProviders);
335             return this;
336         }
337 
338         /**
339          * Builds a {@link CredentialOption}.
340          */
341         @NonNull
build()342         public CredentialOption build() {
343             return new CredentialOption(mType, mCredentialRetrievalData, mCandidateQueryData,
344                     mIsSystemProviderRequired, mAllowedProviders);
345         }
346     }
347 }
348