/* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.credentials; import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN; import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.util.AnnotationValidations; import com.android.internal.util.Preconditions; /** * A request to register a specific type of user credential, potentially launching UI flows to * collect user consent and any other operation needed. */ public final class CreateCredentialRequest implements Parcelable { /** * True/false value to determine if the calling app info should be * sent to the provider at every stage. * * Developers must set this to false if they wish to remove the * {@link android.service.credentials.CallingAppInfo} from the query phase request * that providers receive. Note, that providers will still receive the app info in * the final phase after the user has selected the entry. */ private final boolean mAlwaysSendAppInfoToProvider; /** * The requested credential type. */ @NonNull private final String mType; /** * The full credential creation request data. */ @NonNull private final Bundle mCredentialData; /** * The partial request data that will be sent to the provider during the initial creation * candidate query stage. */ @NonNull private final Bundle mCandidateQueryData; /** * Determines whether the request must only be fulfilled by a system provider. */ private final boolean mIsSystemProviderRequired; /** * The origin of the calling app. Callers of this special API (e.g. browsers) * can set this origin for an app different from their own, to be able to get credentials * on behalf of that app. */ @Nullable private final String mOrigin; /** * Returns the requested credential type. */ @NonNull public String getType() { return mType; } /** * Returns the full credential creation request data. * * For security reason, a provider will receive the request data in two stages. First it gets * a partial request, {@link #getCandidateQueryData()} that do not contain sensitive user * information; it uses this information to provide credential creation candidates that the * [@code CredentialManager] will show to the user. Next, this full request data will be sent to * a provider only if the user further grants the consent by choosing a candidate from the * provider. */ @NonNull public Bundle getCredentialData() { return mCredentialData; } /** * Returns the partial request data that will be sent to the provider during the initial * creation candidate query stage. * * For security reason, a provider will receive the request data in two stages. First it gets * this partial request that do not contain sensitive user information; it uses this information * to provide credential creation candidates that the [@code CredentialManager] will show to * the user. Next, the full request data, {@link #getCredentialData()}, will be sent to a * provider only if the user further grants the consent by choosing a candidate from the * provider. */ @NonNull public Bundle getCandidateQueryData() { return mCandidateQueryData; } /** * Returns true if the request must only be fulfilled by a system provider, and false * otherwise. */ public boolean isSystemProviderRequired() { return mIsSystemProviderRequired; } /** * Return true/false value to determine if the calling app info should always be sent * to providers (if true), or removed from the query phase (if false). */ public boolean alwaysSendAppInfoToProvider() { return mAlwaysSendAppInfoToProvider; } /** * Returns the origin of the calling app if set otherwise returns null. */ @Nullable public String getOrigin() { return mOrigin; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString8(mType); dest.writeBundle(mCredentialData); dest.writeBundle(mCandidateQueryData); dest.writeBoolean(mIsSystemProviderRequired); dest.writeBoolean(mAlwaysSendAppInfoToProvider); dest.writeString8(mOrigin); } @Override public int describeContents() { return 0; } @Override public String toString() { return "CreateCredentialRequest {" + "type=" + mType + ", credentialData=" + mCredentialData + ", candidateQueryData=" + mCandidateQueryData + ", isSystemProviderRequired=" + mIsSystemProviderRequired + ", alwaysSendAppInfoToProvider=" + mAlwaysSendAppInfoToProvider + ", origin=" + mOrigin + "}"; } /** * Constructs a {@link CreateCredentialRequest}. * * @param type the requested credential type * @param credentialData the full credential creation request data * @param candidateQueryData the partial request data that will be sent to the provider * during the initial creation candidate query stage * @param isSystemProviderRequired whether the request must only be fulfilled by a system * provider * @param alwaysSendAppInfoToProvider whether the * {@link android.service.credentials.CallingAppInfo} should be propagated to the provider * at every stage of the request. If set to false, * the calling app info will be removed from * the query phase, and will only be sent along * with the final request, after the user has selected * an entry on the UI. * @param origin the origin of the calling app. Callers of this special setter (e.g. browsers) * can set this origin for an app different from their own, to be able to get * credentials on behalf of that app. * * @throws IllegalArgumentException If type is empty. */ private CreateCredentialRequest( @NonNull String type, @NonNull Bundle credentialData, @NonNull Bundle candidateQueryData, boolean isSystemProviderRequired, boolean alwaysSendAppInfoToProvider, @NonNull String origin) { mType = Preconditions.checkStringNotEmpty(type, "type must not be empty"); mCredentialData = requireNonNull(credentialData, "credentialData must not be null"); mCandidateQueryData = requireNonNull(candidateQueryData, "candidateQueryData must not be null"); mIsSystemProviderRequired = isSystemProviderRequired; mAlwaysSendAppInfoToProvider = alwaysSendAppInfoToProvider; mOrigin = origin; } private CreateCredentialRequest(@NonNull Parcel in) { String type = in.readString8(); Bundle credentialData = in.readBundle(); Bundle candidateQueryData = in.readBundle(); boolean isSystemProviderRequired = in.readBoolean(); boolean alwaysSendAppInfoToProvider = in.readBoolean(); mOrigin = in.readString8(); mType = type; AnnotationValidations.validate(NonNull.class, null, mType); mCredentialData = credentialData; AnnotationValidations.validate(NonNull.class, null, mCredentialData); mCandidateQueryData = candidateQueryData; AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData); mIsSystemProviderRequired = isSystemProviderRequired; mAlwaysSendAppInfoToProvider = alwaysSendAppInfoToProvider; } public static final @NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public CreateCredentialRequest[] newArray(int size) { return new CreateCredentialRequest[size]; } @Override public CreateCredentialRequest createFromParcel(@NonNull Parcel in) { return new CreateCredentialRequest(in); } }; /** A builder for {@link CreateCredentialRequest}. */ public static final class Builder { private boolean mAlwaysSendAppInfoToProvider = true; @NonNull private String mType; @NonNull private final Bundle mCredentialData; @NonNull private final Bundle mCandidateQueryData; private boolean mIsSystemProviderRequired; private String mOrigin; /** * @param type the type of the credential to be stored * @param credentialData the full credential creation request data, which must at minimum * contain the required fields observed at the * {@link androidx.credentials.CreateCredentialRequest} Bundle conversion static methods, * because they are required for properly displaying the system credential selector UI * @param candidateQueryData the partial request data that will be sent to the provider * during the initial creation candidate query stage */ public Builder( @NonNull String type, @NonNull Bundle credentialData, @NonNull Bundle candidateQueryData) { mType = Preconditions.checkStringNotEmpty(type, "type must not be null or empty"); mCredentialData = requireNonNull(credentialData, "credentialData must not be null"); mCandidateQueryData = requireNonNull(candidateQueryData, "candidateQueryData must not be null"); } /** * Sets a true/false value to determine if the calling app info should be * removed from the request that is sent to the providers. * * Developers must set this to false if they wish to remove the * {@link android.service.credentials.CallingAppInfo} from the query phases requests that * providers receive. Note that the calling app info will still be sent in the * final phase after the user has made a selection on the UI. * * If not set, the default value will be true and the calling app info will be * propagated to the providers in every phase. */ @SuppressLint("MissingGetterMatchingBuilder") @NonNull public CreateCredentialRequest.Builder setAlwaysSendAppInfoToProvider(boolean value) { mAlwaysSendAppInfoToProvider = value; return this; } /** * Sets whether the request must only be fulfilled by a system provider. * This defaults to false */ @SuppressLint("MissingGetterMatchingBuilder") @NonNull public CreateCredentialRequest.Builder setIsSystemProviderRequired(boolean value) { mIsSystemProviderRequired = value; return this; } /** * Sets the origin of the calling app. Callers of this special setter (e.g. browsers) * can set this origin for an app different from their own, to be able to get * credentials on behalf of that app. The permission check only happens later when this * instance is passed and processed by the Credential Manager. */ @SuppressLint({"MissingGetterMatchingBuilder", "AndroidFrameworkRequiresPermission"}) @RequiresPermission(CREDENTIAL_MANAGER_SET_ORIGIN) @NonNull public CreateCredentialRequest.Builder setOrigin(@NonNull String origin) { mOrigin = origin; return this; } /** * Builds a {@link GetCredentialRequest}. * * @throws IllegalArgumentException If credentialOptions is empty. */ @NonNull public CreateCredentialRequest build() { Preconditions.checkStringNotEmpty( mType, "type must not be empty"); return new CreateCredentialRequest(mType, mCredentialData, mCandidateQueryData, mIsSystemProviderRequired, mAlwaysSendAppInfoToProvider, mOrigin); } } }