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 import java.util.ArrayList;
35 import java.util.List;
36 
37 /**
38  * A request to retrieve the user credential, potentially launching UI flows to let the user pick
39  * from different credential sources.
40  */
41 public final class GetCredentialRequest implements Parcelable {
42 
43     /**
44      * The list of credential requests.
45      */
46     @NonNull
47     private final List<CredentialOption> mCredentialOptions;
48 
49     /**
50      * The top request level data.
51      */
52     @NonNull
53     private final Bundle mData;
54 
55     /**
56      * The origin of the calling app. Callers of this special API (e.g. browsers)
57      * can set this origin for an app different from their own, to be able to get credentials
58      * on behalf of that app.
59      */
60     @Nullable
61     private String mOrigin;
62 
63     /**
64      * True/False value to determine if the calling app info should be
65      * removed from the request that is sent to the providers.
66      * Developers must set this to false if they wish to remove the
67      * {@link android.service.credentials.CallingAppInfo} from the query phases requests that
68      * providers receive.
69      * If not set, the default value will be true and the calling app info will be
70      * propagated to the providers.
71      */
72     private final boolean mAlwaysSendAppInfoToProvider;
73 
74     /**
75      * Returns the list of credential options to be requested.
76      */
77     @NonNull
getCredentialOptions()78     public List<CredentialOption> getCredentialOptions() {
79         return mCredentialOptions;
80     }
81 
82     /**
83      * Returns the top request level data.
84      */
85     @NonNull
getData()86     public Bundle getData() {
87         return mData;
88     }
89 
90     /**
91      * Returns the origin of the calling app if set otherwise returns null.
92      */
93     @Nullable
getOrigin()94     public String getOrigin() {
95         return mOrigin;
96     }
97 
98     /**
99      * Returns a value to determine if the calling app info should be always
100      * sent to the provider in every phase (if true), or should be removed
101      * from the query phase, and only sent as part of the request in the final phase,
102      * after the user has made a selection on the UI (if false).
103      */
alwaysSendAppInfoToProvider()104     public boolean alwaysSendAppInfoToProvider() {
105         return mAlwaysSendAppInfoToProvider;
106     }
107 
108     @Override
writeToParcel(@onNull Parcel dest, int flags)109     public void writeToParcel(@NonNull Parcel dest, int flags) {
110         dest.writeTypedList(mCredentialOptions, flags);
111         dest.writeBundle(mData);
112         dest.writeBoolean(mAlwaysSendAppInfoToProvider);
113         dest.writeString8(mOrigin);
114     }
115 
116     @Override
describeContents()117     public int describeContents() {
118         return 0;
119     }
120 
121     @Override
toString()122     public String toString() {
123         return "GetCredentialRequest {credentialOption=" + mCredentialOptions
124                 + ", data=" + mData
125                 + ", alwaysSendAppInfoToProvider="
126                 + mAlwaysSendAppInfoToProvider
127                 + ", origin=" + mOrigin
128                 + "}";
129     }
130 
GetCredentialRequest(@onNull List<CredentialOption> credentialOptions, @NonNull Bundle data, @NonNull boolean alwaysSendAppInfoToProvider, String origin)131     private GetCredentialRequest(@NonNull List<CredentialOption> credentialOptions,
132             @NonNull Bundle data, @NonNull boolean alwaysSendAppInfoToProvider, String origin) {
133         Preconditions.checkCollectionNotEmpty(
134                 credentialOptions,
135                 /*valueName=*/ "credentialOptions");
136         Preconditions.checkCollectionElementsNotNull(
137                 credentialOptions,
138                 /*valueName=*/ "credentialOptions");
139         mCredentialOptions = credentialOptions;
140         mData = requireNonNull(data,
141                 "data must not be null");
142         mAlwaysSendAppInfoToProvider = alwaysSendAppInfoToProvider;
143         mOrigin = origin;
144     }
145 
GetCredentialRequest(@onNull Parcel in)146     private GetCredentialRequest(@NonNull Parcel in) {
147         List<CredentialOption> credentialOptions = new ArrayList<CredentialOption>();
148         in.readTypedList(credentialOptions, CredentialOption.CREATOR);
149         mCredentialOptions = credentialOptions;
150         AnnotationValidations.validate(NonNull.class, null, mCredentialOptions);
151 
152         Bundle data = in.readBundle();
153         mData = data;
154         AnnotationValidations.validate(NonNull.class, null, mData);
155 
156         mAlwaysSendAppInfoToProvider = in.readBoolean();
157         mOrigin = in.readString8();
158     }
159 
160     @NonNull public static final Parcelable.Creator<GetCredentialRequest> CREATOR =
161             new Parcelable.Creator<>() {
162                 @Override
163                 public GetCredentialRequest[] newArray(int size) {
164                     return new GetCredentialRequest[size];
165                 }
166 
167                 @Override
168                 public GetCredentialRequest createFromParcel(@NonNull Parcel in) {
169                     return new GetCredentialRequest(in);
170                 }
171             };
172 
173     /** A builder for {@link GetCredentialRequest}. */
174     public static final class Builder {
175 
176         @NonNull
177         private List<CredentialOption> mCredentialOptions = new ArrayList<>();
178 
179         @NonNull
180         private final Bundle mData;
181 
182         @NonNull
183         private boolean mAlwaysSendAppInfoToProvider = true;
184 
185         private String mOrigin;
186 
187         /**
188          * @param data the top request level data
189          */
Builder(@onNull Bundle data)190         public Builder(@NonNull Bundle data) {
191             mData = requireNonNull(data, "data must not be null");
192         }
193 
194         /**
195          * Adds a specific type of {@link CredentialOption}.
196          */
197         @NonNull
addCredentialOption(@onNull CredentialOption credentialOption)198         public Builder addCredentialOption(@NonNull CredentialOption credentialOption) {
199             mCredentialOptions.add(requireNonNull(
200                     credentialOption, "credentialOption must not be null"));
201             return this;
202         }
203 
204         /**
205          * Sets a true/false value to determine if the calling app info should be
206          * removed from the request that is sent to the providers.
207          *
208          * Developers must set this to false if they wish to remove the
209          * {@link android.service.credentials.CallingAppInfo} from the query phases requests that
210          * providers receive. Note that the calling app info will still be sent in the
211          * final phase after the user has made a selection on the UI.
212          *
213          * If not set, the default value will be true and the calling app info will be
214          * propagated to the providers in every phase.
215          */
216         @SuppressLint("MissingGetterMatchingBuilder")
217         @NonNull
setAlwaysSendAppInfoToProvider(boolean value)218         public Builder setAlwaysSendAppInfoToProvider(boolean value) {
219             mAlwaysSendAppInfoToProvider = value;
220             return this;
221         }
222 
223         /**
224          * Sets the list of {@link CredentialOption}.
225          */
226         @NonNull
setCredentialOptions( @onNull List<CredentialOption> credentialOptions)227         public Builder setCredentialOptions(
228                 @NonNull List<CredentialOption> credentialOptions) {
229             Preconditions.checkCollectionElementsNotNull(
230                     credentialOptions,
231                     /*valueName=*/ "credentialOptions");
232             mCredentialOptions = new ArrayList<>(credentialOptions);
233             return this;
234         }
235 
236         /**
237          * Sets the origin of the calling app. Callers of this special setter (e.g. browsers)
238          * can set this origin for an app different from their own, to be able to get
239          * credentials on behalf of that app. The permission check only happens later when this
240          * instance is passed and processed by the Credential Manager.
241          */
242         @SuppressLint({"MissingGetterMatchingBuilder", "AndroidFrameworkRequiresPermission"})
243         @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN)
244         @NonNull
setOrigin(@onNull String origin)245         public Builder setOrigin(@NonNull String origin) {
246             mOrigin = origin;
247             return this;
248         }
249 
250         /**
251          * Builds a {@link GetCredentialRequest}.
252          *
253          * @throws IllegalArgumentException If credentialOptions is empty.
254          */
255         @NonNull
build()256         public GetCredentialRequest build() {
257             Preconditions.checkCollectionNotEmpty(
258                     mCredentialOptions,
259                     /*valueName=*/ "credentialOptions");
260             Preconditions.checkCollectionElementsNotNull(
261                     mCredentialOptions,
262                     /*valueName=*/ "credentialOptions");
263             return new GetCredentialRequest(mCredentialOptions, mData,
264                     mAlwaysSendAppInfoToProvider, mOrigin);
265         }
266     }
267 }
268