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_ORIGIN;
20 
21 import static java.util.Objects.requireNonNull;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.RequiresPermission;
26 import android.annotation.SuppressLint;
27 import android.os.Bundle;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 
31 import com.android.internal.util.AnnotationValidations;
32 import com.android.internal.util.Preconditions;
33 
34 /**
35  * A request to register a specific type of user credential, potentially launching UI flows to
36  * collect user consent and any other operation needed.
37  */
38 public final class CreateCredentialRequest implements Parcelable {
39 
40     /**
41      * True/false value to determine if the calling app info should be
42      * sent to the provider at every stage.
43      *
44      * Developers must set this to false if they wish to remove the
45      * {@link android.service.credentials.CallingAppInfo} from the query phase request
46      * that providers receive. Note, that providers will still receive the app info in
47      * the final phase after the user has selected the entry.
48      */
49     private final boolean mAlwaysSendAppInfoToProvider;
50 
51 
52     /**
53      * The requested credential type.
54      */
55     @NonNull
56     private final String mType;
57 
58     /**
59      * The full credential creation request data.
60      */
61     @NonNull
62     private final Bundle mCredentialData;
63 
64     /**
65      * The partial request data that will be sent to the provider during the initial creation
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      * The origin of the calling app. Callers of this special API (e.g. browsers)
78      * can set this origin for an app different from their own, to be able to get credentials
79      * on behalf of that app.
80      */
81     @Nullable
82     private final String mOrigin;
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 credential creation request data.
94      *
95      * For security reason, a provider will receive the request data in two stages. First it gets
96      * a partial request, {@link #getCandidateQueryData()} that do not contain sensitive user
97      * information; it uses this information to provide credential creation candidates that the
98      * [@code CredentialManager] will show to the user. Next, this full request data will be sent to
99      * a provider only if the user further grants the consent by choosing a candidate from the
100      * provider.
101      */
102     @NonNull
getCredentialData()103     public Bundle getCredentialData() {
104         return mCredentialData;
105     }
106 
107     /**
108      * Returns the partial request data that will be sent to the provider during the initial
109      * creation candidate query stage.
110      *
111      * For security reason, a provider will receive the request data in two stages. First it gets
112      * this partial request that do not contain sensitive user information; it uses this information
113      * to provide credential creation candidates that the [@code CredentialManager] will show to
114      * the user. Next, the full request data, {@link #getCredentialData()}, will be sent to a
115      * provider only if the user further grants the consent by choosing a candidate from the
116      * provider.
117      */
118     @NonNull
getCandidateQueryData()119     public Bundle getCandidateQueryData() {
120         return mCandidateQueryData;
121     }
122 
123     /**
124      * Returns true if the request must only be fulfilled by a system provider, and false
125      * otherwise.
126      */
isSystemProviderRequired()127     public boolean isSystemProviderRequired() {
128         return mIsSystemProviderRequired;
129     }
130 
131     /**
132      * Return true/false value to determine if the calling app info should always be sent
133      * to providers (if true), or removed from the query phase (if false).
134      */
alwaysSendAppInfoToProvider()135     public boolean alwaysSendAppInfoToProvider() {
136         return mAlwaysSendAppInfoToProvider;
137     }
138 
139     /**
140      * Returns the origin of the calling app if set otherwise returns null.
141      */
142     @Nullable
getOrigin()143     public String getOrigin() {
144         return mOrigin;
145     }
146 
147     @Override
writeToParcel(@onNull Parcel dest, int flags)148     public void writeToParcel(@NonNull Parcel dest, int flags) {
149         dest.writeString8(mType);
150         dest.writeBundle(mCredentialData);
151         dest.writeBundle(mCandidateQueryData);
152         dest.writeBoolean(mIsSystemProviderRequired);
153         dest.writeBoolean(mAlwaysSendAppInfoToProvider);
154         dest.writeString8(mOrigin);
155     }
156 
157     @Override
describeContents()158     public int describeContents() {
159         return 0;
160     }
161 
162     @Override
toString()163     public String toString() {
164         return "CreateCredentialRequest {"
165                 + "type=" + mType
166                 + ", credentialData=" + mCredentialData
167                 + ", candidateQueryData=" + mCandidateQueryData
168                 + ", isSystemProviderRequired=" + mIsSystemProviderRequired
169                 + ", alwaysSendAppInfoToProvider="
170                 + mAlwaysSendAppInfoToProvider
171                 + ", origin=" + mOrigin
172                 + "}";
173     }
174 
175     /**
176      * Constructs a {@link CreateCredentialRequest}.
177      *
178      * @param type the requested credential type
179      * @param credentialData the full credential creation request data
180      * @param candidateQueryData the partial request data that will be sent to the provider
181      *                           during the initial creation candidate query stage
182      * @param isSystemProviderRequired whether the request must only be fulfilled by a system
183      *                                provider
184      * @param alwaysSendAppInfoToProvider whether the
185      * {@link android.service.credentials.CallingAppInfo} should be propagated to the provider
186      *                                    at every stage of the request. If set to false,
187      *                                    the calling app info will be removed from
188      *                                    the query phase, and will only be sent along
189      *                                    with the final request, after the user has selected
190      *                                    an entry on the UI.
191      * @param origin the origin of the calling app. Callers of this special setter (e.g. browsers)
192      *               can set this origin for an app different from their own, to be able to get
193      *               credentials on behalf of that app.
194      *
195      * @throws IllegalArgumentException If type is empty.
196      */
CreateCredentialRequest( @onNull String type, @NonNull Bundle credentialData, @NonNull Bundle candidateQueryData, boolean isSystemProviderRequired, boolean alwaysSendAppInfoToProvider, @NonNull String origin)197     private CreateCredentialRequest(
198             @NonNull String type,
199             @NonNull Bundle credentialData,
200             @NonNull Bundle candidateQueryData,
201             boolean isSystemProviderRequired,
202             boolean alwaysSendAppInfoToProvider,
203             @NonNull String origin) {
204         mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
205         mCredentialData = requireNonNull(credentialData, "credentialData must not be null");
206         mCandidateQueryData = requireNonNull(candidateQueryData,
207                 "candidateQueryData must not be null");
208         mIsSystemProviderRequired = isSystemProviderRequired;
209         mAlwaysSendAppInfoToProvider = alwaysSendAppInfoToProvider;
210         mOrigin = origin;
211     }
212 
CreateCredentialRequest(@onNull Parcel in)213     private CreateCredentialRequest(@NonNull Parcel in) {
214         String type = in.readString8();
215         Bundle credentialData = in.readBundle();
216         Bundle candidateQueryData = in.readBundle();
217         boolean isSystemProviderRequired = in.readBoolean();
218         boolean alwaysSendAppInfoToProvider = in.readBoolean();
219         mOrigin = in.readString8();
220 
221         mType = type;
222         AnnotationValidations.validate(NonNull.class, null, mType);
223         mCredentialData = credentialData;
224         AnnotationValidations.validate(NonNull.class, null, mCredentialData);
225         mCandidateQueryData = candidateQueryData;
226         AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
227         mIsSystemProviderRequired = isSystemProviderRequired;
228         mAlwaysSendAppInfoToProvider = alwaysSendAppInfoToProvider;
229     }
230 
231     public static final @NonNull Parcelable.Creator<CreateCredentialRequest> CREATOR =
232             new Parcelable.Creator<CreateCredentialRequest>() {
233         @Override
234         public CreateCredentialRequest[] newArray(int size) {
235             return new CreateCredentialRequest[size];
236         }
237 
238         @Override
239         public CreateCredentialRequest createFromParcel(@NonNull Parcel in) {
240             return new CreateCredentialRequest(in);
241         }
242     };
243 
244     /** A builder for {@link CreateCredentialRequest}. */
245     public static final class Builder {
246 
247         private boolean mAlwaysSendAppInfoToProvider = true;
248 
249         @NonNull
250         private String mType;
251 
252         @NonNull
253         private final Bundle mCredentialData;
254 
255         @NonNull
256         private final Bundle mCandidateQueryData;
257 
258         private boolean mIsSystemProviderRequired;
259 
260         private String mOrigin;
261 
262         /**
263          * @param type the type of the credential to be stored
264          * @param credentialData the full credential creation request data, which must at minimum
265          * contain the required fields observed at the
266          * {@link androidx.credentials.CreateCredentialRequest} Bundle conversion static methods,
267          * because they are required for properly displaying the system credential selector UI
268          * @param candidateQueryData the partial request data that will be sent to the provider
269          *                           during the initial creation candidate query stage
270          */
Builder( @onNull String type, @NonNull Bundle credentialData, @NonNull Bundle candidateQueryData)271         public Builder(
272                 @NonNull String type,
273                 @NonNull Bundle credentialData,
274                 @NonNull Bundle candidateQueryData) {
275             mType = Preconditions.checkStringNotEmpty(type,
276                     "type must not be null or empty");
277             mCredentialData = requireNonNull(credentialData,
278                     "credentialData must not be null");
279             mCandidateQueryData = requireNonNull(candidateQueryData,
280                     "candidateQueryData must not be null");
281         }
282 
283         /**
284          * Sets a true/false value to determine if the calling app info should be
285          * removed from the request that is sent to the providers.
286          *
287          * Developers must set this to false if they wish to remove the
288          * {@link android.service.credentials.CallingAppInfo} from the query phases requests that
289          * providers receive. Note that the calling app info will still be sent in the
290          * final phase after the user has made a selection on the UI.
291          *
292          * If not set, the default value will be true and the calling app info will be
293          * propagated to the providers in every phase.
294          */
295         @SuppressLint("MissingGetterMatchingBuilder")
296         @NonNull
setAlwaysSendAppInfoToProvider(boolean value)297         public CreateCredentialRequest.Builder setAlwaysSendAppInfoToProvider(boolean value) {
298             mAlwaysSendAppInfoToProvider = value;
299             return this;
300         }
301 
302         /**
303          * Sets whether the request must only be fulfilled by a system provider.
304          * This defaults to false
305          */
306         @SuppressLint("MissingGetterMatchingBuilder")
307         @NonNull
setIsSystemProviderRequired(boolean value)308         public CreateCredentialRequest.Builder setIsSystemProviderRequired(boolean value) {
309             mIsSystemProviderRequired = value;
310             return this;
311         }
312 
313         /**
314          * Sets the origin of the calling app. Callers of this special setter (e.g. browsers)
315          * can set this origin for an app different from their own, to be able to get
316          * credentials on behalf of that app. The permission check only happens later when this
317          * instance is passed and processed by the Credential Manager.
318          */
319         @SuppressLint({"MissingGetterMatchingBuilder", "AndroidFrameworkRequiresPermission"})
320         @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN)
321         @NonNull
setOrigin(@onNull String origin)322         public CreateCredentialRequest.Builder setOrigin(@NonNull String origin) {
323             mOrigin = origin;
324             return this;
325         }
326 
327         /**
328          * Builds a {@link GetCredentialRequest}.
329          *
330          * @throws IllegalArgumentException If credentialOptions is empty.
331          */
332         @NonNull
build()333         public CreateCredentialRequest build() {
334             Preconditions.checkStringNotEmpty(
335                     mType,
336                     "type must not be empty");
337 
338             return new CreateCredentialRequest(mType, mCredentialData, mCandidateQueryData,
339                     mIsSystemProviderRequired, mAlwaysSendAppInfoToProvider, mOrigin);
340         }
341     }
342 }
343