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 androidx.credentials
18 
19 import android.content.ComponentName
20 import android.os.Bundle
21 import androidx.annotation.RequiresApi
22 import androidx.credentials.internal.FrameworkClassParsingException
23 
24 /**
25  * Encapsulates a request to get a user credential.
26  *
27  * An application can construct such a request by adding one or more types of [CredentialOption],
28  * and then call [CredentialManager.getCredential] to launch framework UI flows to allow the user to
29  * consent to using a previously saved credential for the given application.
30  *
31  * @param credentialOptions the list of [CredentialOption] from which the user can choose one to
32  *   authenticate to the app
33  * @param origin the origin of a different application if the request is being made on behalf of
34  *   that application (Note: for API level >=34, setting a non-null value for this parameter, will
35  *   throw a SecurityException if android.permission.CREDENTIAL_MANAGER_SET_ORIGIN is not present)
36  * @param preferIdentityDocUi the value which signals if the UI should be tailored to display an
37  *   identity document like driver license etc
38  * @param preferUiBrandingComponentName a service [ComponentName] from which the Credential Selector
39  *   UI will pull its label and icon to render top level branding (Note: your app must have the
40  *   permission android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS to specify this, or it
41  *   would not take effect; also this bit may not take effect for Android API level 33 and below,
42  *   depending on the pre-34 provider(s) you have chosen
43  * @param preferImmediatelyAvailableCredentials true if you prefer the operation to return
44  *   immediately when there is no available credentials instead of falling back to discovering
45  *   remote options, and false (default) otherwise
46  * @property credentialOptions the list of [CredentialOption] from which the user can choose one to
47  *   authenticate to the app
48  * @property origin the origin of a different application if the request is being made on behalf of
49  *   that application. For API level >=34, setting a non-null value for this parameter, will throw a
50  *   SecurityException if android.permission.CREDENTIAL_MANAGER_SET_ORIGIN is not present.
51  * @property preferIdentityDocUi the value which signals if the UI should be tailored to display an
52  *   identity document like driver license etc.
53  * @property preferUiBrandingComponentName a service [ComponentName] from which the Credential
54  *   Selector UI will pull its label and icon to render top level branding. Your app must have the
55  *   permission android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS to specify this, or it
56  *   would not take effect. Notice that this bit may not take effect for Android API level 33 and
57  *   below, depending on the pre-34 provider(s) you have chosen.
58  * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return
59  *   immediately when there is no available credentials instead of falling back to discovering
60  *   remote options, and false (default) otherwise
61  * @throws IllegalArgumentException If [credentialOptions] is empty or contains
62  *   [GetRestoreCredentialOption] with another option (i.e. [GetPasswordOption] or
63  *   [GetPublicKeyCredentialOption]).
64  */
65 @OptIn(ExperimentalDigitalCredentialApi::class)
66 class GetCredentialRequest
67 @JvmOverloads
68 constructor(
69     val credentialOptions: List<CredentialOption>,
70     val origin: String? = null,
71     val preferIdentityDocUi: Boolean = false,
72     val preferUiBrandingComponentName: ComponentName? = null,
73     @get:JvmName("preferImmediatelyAvailableCredentials")
74     val preferImmediatelyAvailableCredentials: Boolean = false,
75 ) {
76 
77     init {
<lambda>null78         require(credentialOptions.isNotEmpty()) { "credentialOptions should not be empty" }
79         if (credentialOptions.size > 1) {
80             val digitalCredentialOptionCount =
<lambda>null81                 credentialOptions.count { it is GetDigitalCredentialOption }
82             if (
83                 digitalCredentialOptionCount > 0 &&
84                     digitalCredentialOptionCount != credentialOptions.size
85             ) {
86                 throw IllegalArgumentException(
87                     "Digital Credential Option cannot be used with other credential option."
88                 )
89             }
optionnull90             for (option in credentialOptions) {
91                 if (option is GetRestoreCredentialOption) {
92                     throw IllegalArgumentException(
93                         "Only a single GetRestoreCredentialOption should be provided."
94                     )
95                 }
96             }
97         }
98     }
99 
100     /** A builder for [GetCredentialRequest]. */
101     class Builder {
102         private var credentialOptions: MutableList<CredentialOption> = mutableListOf()
103         private var origin: String? = null
104         private var preferIdentityDocUi: Boolean = false
105         private var preferImmediatelyAvailableCredentials: Boolean = false
106         private var preferUiBrandingComponentName: ComponentName? = null
107 
108         /** Adds a specific type of [CredentialOption]. */
addCredentialOptionnull109         fun addCredentialOption(credentialOption: CredentialOption): Builder {
110             credentialOptions.add(credentialOption)
111             return this
112         }
113 
114         /** Sets the list of [CredentialOption]. */
setCredentialOptionsnull115         fun setCredentialOptions(credentialOptions: List<CredentialOption>): Builder {
116             this.credentialOptions = credentialOptions.toMutableList()
117             return this
118         }
119 
120         /**
121          * Sets the [origin] of a different application if the request is being made on behalf of
122          * that application. For API level >=34, setting a non-null value for this parameter, will
123          * throw a SecurityException if android.permission.CREDENTIAL_MANAGER_SET_ORIGIN is not
124          * present.
125          */
setOriginnull126         fun setOrigin(origin: String): Builder {
127             this.origin = origin
128             return this
129         }
130 
131         /**
132          * Sets whether you prefer the operation to return immediately when there is no available
133          * credentials instead of falling back to discovering remote options. The default value is
134          * false.
135          */
136         @Suppress("MissingGetterMatchingBuilder")
setPreferImmediatelyAvailableCredentialsnull137         fun setPreferImmediatelyAvailableCredentials(
138             preferImmediatelyAvailableCredentials: Boolean
139         ): Builder {
140             this.preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials
141             return this
142         }
143 
144         /**
145          * Sets service [ComponentName] from which the Credential Selector UI will pull its label
146          * and icon to render top level branding. Your app must have the permission
147          * android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS to specify this, or it would
148          * not take effect. Notice that this bit may not take effect for Android API level 33 and
149          * below, depending on the pre-34 provider(s) you have chosen.
150          */
setPreferUiBrandingComponentNamenull151         fun setPreferUiBrandingComponentName(component: ComponentName?): Builder {
152             this.preferUiBrandingComponentName = component
153             return this
154         }
155 
156         /**
157          * Sets the [Boolean] preferIdentityDocUi to true if the requester wants to prefer using a
158          * UI suited for Identity Documents like mDocs, Driving License etc.
159          */
160         @Suppress("MissingGetterMatchingBuilder")
setPreferIdentityDocUinull161         fun setPreferIdentityDocUi(preferIdentityDocUi: Boolean): Builder {
162             this.preferIdentityDocUi = preferIdentityDocUi
163             return this
164         }
165 
166         /**
167          * Builds a [GetCredentialRequest].
168          *
169          * @throws IllegalArgumentException If [credentialOptions] is empty
170          */
buildnull171         fun build(): GetCredentialRequest {
172             return GetCredentialRequest(
173                 credentialOptions.toList(),
174                 origin,
175                 preferIdentityDocUi,
176                 preferUiBrandingComponentName,
177                 preferImmediatelyAvailableCredentials
178             )
179         }
180     }
181 
182     companion object {
183         internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
184             "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS"
185         private const val BUNDLE_KEY_PREFER_IDENTITY_DOC_UI =
186             "androidx.credentials.BUNDLE_KEY_PREFER_IDENTITY_DOC_UI"
187         private const val BUNDLE_KEY_PREFER_UI_BRANDING_COMPONENT_NAME =
188             "androidx.credentials.BUNDLE_KEY_PREFER_UI_BRANDING_COMPONENT_NAME"
189 
190         /**
191          * Returns the request metadata as a `Bundle`.
192          *
193          * This API should only be used by OEM services and library groups.
194          *
195          * Note: this is not the equivalent of the complete request itself. For example, it does not
196          * include the request's `credentialOptions` or `origin`.
197          */
198         @JvmStatic
getRequestMetadataBundlenull199         fun getRequestMetadataBundle(request: GetCredentialRequest): Bundle {
200             val bundle = Bundle()
201             bundle.putBoolean(BUNDLE_KEY_PREFER_IDENTITY_DOC_UI, request.preferIdentityDocUi)
202             bundle.putBoolean(
203                 BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
204                 request.preferImmediatelyAvailableCredentials
205             )
206             bundle.putParcelable(
207                 BUNDLE_KEY_PREFER_UI_BRANDING_COMPONENT_NAME,
208                 request.preferUiBrandingComponentName
209             )
210             return bundle
211         }
212 
213         /**
214          * Parses the [request] into an instance of [GetCredentialRequest].
215          *
216          * It is recommended to construct a GetCredentialRequest by direct constructor calls,
217          * instead of using this API. This API should only be used by a small subset of system apps
218          * that reconstruct an existing object for user interactions such as collecting consents.
219          *
220          * @param request the framework GetCredentialRequest object
221          */
222         @RequiresApi(34)
223         @JvmStatic
createFromnull224         fun createFrom(request: android.credentials.GetCredentialRequest): GetCredentialRequest {
225             return createFrom(
226                 request.credentialOptions.map { CredentialOption.createFrom(it) },
227                 request.origin,
228                 request.data
229             )
230         }
231 
232         /**
233          * Parses the raw data into an instance of [GetCredentialRequest].
234          *
235          * It is recommended to construct a GetCredentialRequest by direct constructor calls,
236          * instead of using this API. This API should only be used by a small subset of system apps
237          * that reconstruct an existing object for user interactions such as collecting consents.
238          *
239          * @param credentialOptions matches [GetCredentialRequest.credentialOptions]
240          * @param origin matches [GetCredentialRequest.origin]
241          * @param metadata request metadata serialized as a Bundle using [getRequestMetadataBundle]
242          */
243         @JvmStatic
createFromnull244         fun createFrom(
245             credentialOptions: List<CredentialOption>,
246             origin: String?,
247             metadata: Bundle
248         ): GetCredentialRequest {
249             try {
250                 val preferIdentityDocUi = metadata.getBoolean(BUNDLE_KEY_PREFER_IDENTITY_DOC_UI)
251                 val preferImmediatelyAvailableCredentials =
252                     metadata.getBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS)
253                 @Suppress("DEPRECATION")
254                 val preferUiBrandingComponentName =
255                     metadata.getParcelable<ComponentName>(
256                         BUNDLE_KEY_PREFER_UI_BRANDING_COMPONENT_NAME
257                     )
258                 val getCredentialBuilder =
259                     Builder()
260                         .setCredentialOptions(credentialOptions)
261                         .setPreferIdentityDocUi(preferIdentityDocUi)
262                         .setPreferUiBrandingComponentName(preferUiBrandingComponentName)
263                         .setPreferImmediatelyAvailableCredentials(
264                             preferImmediatelyAvailableCredentials
265                         )
266                 if (origin != null) {
267                     getCredentialBuilder.setOrigin(origin)
268                 }
269                 return getCredentialBuilder.build()
270             } catch (e: Exception) {
271                 throw FrameworkClassParsingException()
272             }
273         }
274     }
275 }
276