From 0e06b8c27679262e7c746e974feba87aba51ee56 Mon Sep 17 00:00:00 2001 From: Prince Mathew <17837162+pmathew92@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:17:01 +0530 Subject: [PATCH] Revert "Added Passkey authentication support" --- EXAMPLES.md | 3 +- auth0/build.gradle | 12 +- .../authentication/AuthenticationAPIClient.kt | 139 +------ .../authentication/ParameterBuilder.kt | 1 - .../android/provider/PasskeyAuthProvider.kt | 224 ----------- .../auth0/android/provider/PasskeyManager.kt | 239 ------------ .../auth0/android/provider/WebAuthProvider.kt | 8 +- .../request/PublicKeyCredentialRequest.kt | 45 --- .../java/com/auth0/android/request/Request.kt | 12 - .../android/request/UserMetadataRequest.kt | 13 - .../internal/BaseAuthenticationRequest.kt | 23 +- .../android/request/internal/BaseRequest.kt | 2 +- .../result/PasskeyChallengeResponse.kt | 22 -- .../result/PasskeyRegistrationResponse.kt | 56 --- .../AuthenticationAPIClientTest.kt | 97 +---- .../request/AuthenticationRequestMock.java | 7 - .../authentication/request/RequestMock.java | 6 - .../android/provider/PasskeyManagerTest.kt | 362 ------------------ .../util/AuthenticationAPIMockServer.kt | 51 --- proguard/proguard-jetpack.pro | 6 - sample/build.gradle | 4 +- .../com/auth0/sample/DatabaseLoginFragment.kt | 55 --- .../res/layout/fragment_database_login.xml | 39 -- sample/src/main/res/values/strings.xml | 4 +- 24 files changed, 44 insertions(+), 1386 deletions(-) delete mode 100644 auth0/src/main/java/com/auth0/android/provider/PasskeyAuthProvider.kt delete mode 100644 auth0/src/main/java/com/auth0/android/provider/PasskeyManager.kt delete mode 100644 auth0/src/main/java/com/auth0/android/request/PublicKeyCredentialRequest.kt delete mode 100644 auth0/src/main/java/com/auth0/android/request/UserMetadataRequest.kt delete mode 100644 auth0/src/main/java/com/auth0/android/result/PasskeyChallengeResponse.kt delete mode 100644 auth0/src/main/java/com/auth0/android/result/PasskeyRegistrationResponse.kt delete mode 100644 auth0/src/test/java/com/auth0/android/provider/PasskeyManagerTest.kt delete mode 100644 proguard/proguard-jetpack.pro diff --git a/EXAMPLES.md b/EXAMPLES.md index 1ef2da825..78c595146 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -1242,5 +1242,4 @@ You might encounter errors similar to `PKIX path building failed: sun.security.p The rules should be applied automatically if your application is using `minifyEnabled = true`. If you want to include them manually check the [proguard directory](proguard). By default you should at least use the following files: * `proguard-okio.pro` -* `proguard-gson.pro` -* `proguard-jetpack.pro` \ No newline at end of file +* `proguard-gson.pro` \ No newline at end of file diff --git a/auth0/build.gradle b/auth0/build.gradle index 454560ae8..22897333c 100644 --- a/auth0/build.gradle +++ b/auth0/build.gradle @@ -34,18 +34,18 @@ version = getVersionFromFile() logger.lifecycle("Using version ${version} for ${name}") android { - compileSdkVersion 34 + compileSdkVersion 31 defaultConfig { minSdkVersion 21 - targetSdkVersion 34 + targetSdkVersion 31 versionCode 1 versionName project.version buildConfigField "String", "LIBRARY_NAME", "\"$project.rootProject.name\"" buildConfigField "String", "VERSION_NAME", "\"${project.version}\"" - consumerProguardFiles '../proguard/proguard-gson.pro', '../proguard/proguard-okio.pro', '../proguard/proguard-jetpack.pro' + consumerProguardFiles '../proguard/proguard-gson.pro', '../proguard/proguard-okio.pro' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { @@ -77,14 +77,13 @@ ext { powermockVersion = '2.0.9' coroutinesVersion = '1.6.2' biometricLibraryVersion = '1.1.0' - credentialManagerVersion = "1.3.0" } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation 'androidx.core:core-ktx:1.6.0' - implementation 'androidx.appcompat:appcompat:1.6.0' + implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.browser:browser:1.4.0' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion" implementation "com.squareup.okhttp3:okhttp:$okhttpVersion" @@ -111,9 +110,6 @@ dependencies { testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion" testImplementation "androidx.biometric:biometric:$biometricLibraryVersion" - - implementation "androidx.credentials:credentials-play-services-auth:$credentialManagerVersion" - implementation "androidx.credentials:credentials:$credentialManagerVersion" } apply from: rootProject.file('gradle/jacoco.gradle') diff --git a/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt b/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt index bf72e2017..ecac17918 100755 --- a/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt @@ -12,8 +12,6 @@ import com.auth0.android.request.internal.ResponseUtils.isNetworkError import com.auth0.android.result.Challenge import com.auth0.android.result.Credentials import com.auth0.android.result.DatabaseUser -import com.auth0.android.result.PasskeyChallengeResponse -import com.auth0.android.result.PasskeyRegistrationResponse import com.auth0.android.result.UserProfile import com.google.gson.Gson import okhttp3.HttpUrl.Companion.toHttpUrl @@ -153,102 +151,6 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe return loginWithToken(parameters) } - - /** - * Log in a user using passkeys. - * This should be called after the client has received the Passkey challenge and Auth-session from the server . - * Requires the client to have the **Passkey** Grant Type enabled. See [Client Grant Types](https://auth0.com/docs/clients/client-grant-types) - * to learn how to enable it. - * - * @param authSession the auth session received from the server as part of the public challenge request. - * @param authResponse the public key credential response to be sent to the server - * @param parameters additional parameters to be sent as part of the request - * @return a request to configure and start that will yield [Credentials] - */ - internal fun signinWithPasskey( - authSession: String, - authResponse: PublicKeyCredentialResponse, - parameters: Map - ): AuthenticationRequest { - val params = ParameterBuilder.newBuilder().apply { - setGrantType(ParameterBuilder.GRANT_TYPE_PASSKEY) - set(AUTH_SESSION_KEY, authSession) - addAll(parameters) - }.asDictionary() - - return loginWithToken(params) - .addParameter( - AUTH_RESPONSE_KEY, - Gson().toJsonTree(authResponse) - ) as AuthenticationRequest - } - - - /** - * Register a user and returns a challenge. - * Requires the client to have the **Passkey** Grant Type enabled. See [Client Grant Types](https://auth0.com/docs/clients/client-grant-types) - * to learn how to enable it. - * - * @param userMetadata user information of the client - * @param parameters additional parameter to be sent as part of the request - * @return a request to configure and start that will yield [PasskeyRegistrationResponse] - */ - internal fun signupWithPasskey( - userMetadata: UserMetadataRequest, - parameters: Map, - ): Request { - val user = Gson().toJsonTree(userMetadata) - val url = auth0.getDomainUrl().toHttpUrl().newBuilder() - .addPathSegment(PASSKEY_PATH) - .addPathSegment(REGISTER_PATH) - .build() - - val params = ParameterBuilder.newBuilder().apply { - setClientId(clientId) - parameters[ParameterBuilder.REALM_KEY]?.let { - setRealm(it) - } - }.asDictionary() - - val passkeyRegistrationAdapter: JsonAdapter = GsonAdapter( - PasskeyRegistrationResponse::class.java, gson - ) - val post = factory.post(url.toString(), passkeyRegistrationAdapter) - .addParameters(params) as BaseRequest - post.addParameter(USER_PROFILE_KEY, user) - return post - } - - - /** - * Request for a challenge to initiate a passkey login flow - * Requires the client to have the **Passkey** Grant Type enabled. See [Client Grant Types](https://auth0.com/docs/clients/client-grant-types) - * to learn how to enable it. - * - * @param realm An optional connection name - * @return a request to configure and start that will yield [PasskeyChallengeResponse] - */ - internal fun passkeyChallenge( - realm: String? - ): Request { - val url = auth0.getDomainUrl().toHttpUrl().newBuilder() - .addPathSegment(PASSKEY_PATH) - .addPathSegment(CHALLENGE_PATH) - .build() - - val parameters = ParameterBuilder.newBuilder().apply { - setClientId(clientId) - realm?.let { setRealm(it) } - }.asDictionary() - - val passkeyChallengeAdapter: JsonAdapter = GsonAdapter( - PasskeyChallengeResponse::class.java, gson - ) - - return factory.post(url.toString(), passkeyChallengeAdapter) - .addParameters(parameters) - } - /** * Log in a user using an Out Of Band authentication code after they have received the 'mfa_required' error. * The MFA token tells the server the username or email, password, and realm values sent on the first request. @@ -793,7 +695,8 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe val parameters = ParameterBuilder.newBuilder() .setClientId(clientId) .setGrantType(ParameterBuilder.GRANT_TYPE_AUTHORIZATION_CODE) - .set(OAUTH_CODE_KEY, authorizationCode).set(REDIRECT_URI_KEY, redirectUri) + .set(OAUTH_CODE_KEY, authorizationCode) + .set(REDIRECT_URI_KEY, redirectUri) .set("code_verifier", codeVerifier) .asDictionary() val url = auth0.getDomainUrl().toHttpUrl().newBuilder() @@ -833,26 +736,26 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe .addPathSegment(OAUTH_PATH) .addPathSegment(TOKEN_PATH) .build() - val requestParameters = - ParameterBuilder.newBuilder() - .setClientId(clientId) - .addAll(parameters) - .asDictionary() + val requestParameters = ParameterBuilder.newBuilder() + .setClientId(clientId) + .addAll(parameters) + .asDictionary() val credentialsAdapter: JsonAdapter = GsonAdapter( Credentials::class.java, gson ) val request = BaseAuthenticationRequest( - factory.post(url.toString(), credentialsAdapter), clientId, baseURL + factory.post(url.toString(), credentialsAdapter), + clientId, + baseURL ) request.addParameters(requestParameters) return request } private fun profileRequest(): Request { - val url = - auth0.getDomainUrl().toHttpUrl().newBuilder() - .addPathSegment(USER_INFO_PATH) - .build() + val url = auth0.getDomainUrl().toHttpUrl().newBuilder() + .addPathSegment(USER_INFO_PATH) + .build() val userProfileAdapter: JsonAdapter = GsonAdapter( UserProfile::class.java, gson ) @@ -879,9 +782,6 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe private const val SUBJECT_TOKEN_KEY = "subject_token" private const val SUBJECT_TOKEN_TYPE_KEY = "subject_token_type" private const val USER_METADATA_KEY = "user_metadata" - private const val AUTH_SESSION_KEY = "auth_session" - private const val AUTH_RESPONSE_KEY = "authn_response" - private const val USER_PROFILE_KEY = "user_profile" private const val SIGN_UP_PATH = "signup" private const val DB_CONNECTIONS_PATH = "dbconnections" private const val CHANGE_PASSWORD_PATH = "change_password" @@ -893,8 +793,6 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe private const val REVOKE_PATH = "revoke" private const val MFA_PATH = "mfa" private const val CHALLENGE_PATH = "challenge" - private const val PASSKEY_PATH = "passkey" - private const val REGISTER_PATH = "register" private const val HEADER_AUTHORIZATION = "Authorization" private const val WELL_KNOWN_PATH = ".well-known" private const val JWKS_FILE_PATH = "jwks.json" @@ -902,14 +800,17 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe val mapAdapter = forMap(GsonProvider.gson) return object : ErrorAdapter { override fun fromRawResponse( - statusCode: Int, bodyText: String, headers: Map> + statusCode: Int, + bodyText: String, + headers: Map> ): AuthenticationException { return AuthenticationException(bodyText, statusCode) } @Throws(IOException::class) override fun fromJsonResponse( - statusCode: Int, reader: Reader + statusCode: Int, + reader: Reader ): AuthenticationException { val values = mapAdapter.fromJson(reader) return AuthenticationException(values, statusCode) @@ -918,11 +819,13 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe override fun fromException(cause: Throwable): AuthenticationException { if (isNetworkError(cause)) { return AuthenticationException( - "Failed to execute the network request", NetworkErrorException(cause) + "Failed to execute the network request", + NetworkErrorException(cause) ) } return AuthenticationException( - "Something went wrong", Auth0Exception("Something went wrong", cause) + "Something went wrong", + Auth0Exception("Something went wrong", cause) ) } } diff --git a/auth0/src/main/java/com/auth0/android/authentication/ParameterBuilder.kt b/auth0/src/main/java/com/auth0/android/authentication/ParameterBuilder.kt index 511334f5d..3364e111a 100755 --- a/auth0/src/main/java/com/auth0/android/authentication/ParameterBuilder.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/ParameterBuilder.kt @@ -159,7 +159,6 @@ public class ParameterBuilder private constructor(parameters: Map = mutableMapOf() - - /** - * Specify the scope for this request. - * - * @param scope to request - * @return the current builder instance - */ - public fun setScope(scope: String): SignInBuilder = apply { - parameters[ParameterBuilder.SCOPE_KEY] = scope - } - - /** - * Specify the custom audience for this request. - * - * @param audience to use in this request - * @return the current builder instance - */ - public fun setAudience(audience: String): SignInBuilder = apply { - parameters[ParameterBuilder.AUDIENCE_KEY] = audience - } - - /** - * Specify the realm for this request - * - * @param realm to use in this request - * @return the current builder instance - */ - public fun setRealm(realm: String): SignInBuilder = apply { - parameters[ParameterBuilder.REALM_KEY] = realm - } - - /** - * Request user authentication using passkey. The result will be received in the callback. - * - * @param context context to run the authentication - * @param callback to receive the result - * @param executor optional executor to run the public key credential response creation - */ - public fun start( - context: Context, - callback: Callback, - executor: Executor = Executors.newSingleThreadExecutor() - ) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { - Log.w(TAG, "Requires Android 9 or higher to use passkey authentication ") - val ex = AuthenticationException( - "Requires Android 9 or higher" - ) - callback.onFailure(ex) - return - } - val passkeyManager = - PasskeyManager( - AuthenticationAPIClient(auth0), - CredentialManager.create(context) - ) - passkeyManager.signin(context, parameters, callback, executor) - } - } - - - public class SignupBuilder internal constructor(private val auth0: Auth0) { - private var username: String? = null - private var email: String? = null - private var name: String? = null - private var phoneNumber: String? = null - - private val parameters: MutableMap = mutableMapOf() - - /** - * Specify the realm for this request - * - * @param realm to use in this request - * @return the current builder instance - */ - public fun setRealm(realm: String): SignupBuilder = apply { - parameters[ParameterBuilder.REALM_KEY] = realm - } - - /** - * Specify the email for the user. - * Email can be optional,required or forbidden depending on the attribute configuration for the database - * - * @param email to be set - * @return the current builder instance - */ - public fun setEmail(email: String): SignupBuilder = apply { - this.email = email - } - - /** - * Specify the username for the user. - * Username can be optional,required or forbidden depending on the attribute configuration for the database - * - * @param username to be set - * @return the current builder instance - */ - public fun setUserName(username: String): SignupBuilder = apply { - this.username = username - } - - /** - * Specify the name for the user. - * Name can be optional,required or forbidden depending on the attribute configuration for the database - * - * @param name to be set - * @return the current builder instance - */ - public fun setName(name: String): SignupBuilder = apply { - this.name = name - } - - /** - * Specify the phone number for the user - * Phone number can be optional,required or forbidden depending on the attribute configuration for the database - * - * @param number to be set - * @return the current builder instance - */ - public fun setPhoneNumber(number: String): SignupBuilder = apply { - this.phoneNumber = number - } - - /** - * Specify the scope for this request. - * - * @param scope to request - * @return the current builder instance - */ - public fun setScope(scope: String): SignupBuilder = apply { - parameters[ParameterBuilder.SCOPE_KEY] = scope - } - - /** - * Specify the custom audience for this request. - * - * @param audience to use in this request - * @return the current builder instance - */ - public fun setAudience(audience: String): SignupBuilder = apply { - parameters[ParameterBuilder.AUDIENCE_KEY] = audience - } - - /** - * Request user signup and authentication using passkey. The result will be received in the callback. - * - * @param context context to run the authentication - * @param callback to receive the result - * @param executor optional executor to run the public key credential response creation - */ - public fun start( - context: Context, - callback: Callback, - executor: Executor = Executors.newSingleThreadExecutor() - ) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { - Log.w(TAG, "Requires Android 9 or higher to use passkey authentication ") - val ex = AuthenticationException( - "Requires Android 9 or higher" - ) - callback.onFailure(ex) - return - } - val passkeyManager = - PasskeyManager( - AuthenticationAPIClient(auth0), - CredentialManager.create(context) - ) - val userMetadata = UserMetadataRequest(email, phoneNumber, username, name) - passkeyManager.signup( - context, userMetadata, parameters, callback, executor - ) - } - } -} \ No newline at end of file diff --git a/auth0/src/main/java/com/auth0/android/provider/PasskeyManager.kt b/auth0/src/main/java/com/auth0/android/provider/PasskeyManager.kt deleted file mode 100644 index 1b68fd6b3..000000000 --- a/auth0/src/main/java/com/auth0/android/provider/PasskeyManager.kt +++ /dev/null @@ -1,239 +0,0 @@ -package com.auth0.android.provider - -import android.annotation.SuppressLint -import android.content.Context -import android.os.Build -import android.os.CancellationSignal -import android.util.Log -import androidx.annotation.RequiresApi -import androidx.credentials.CreateCredentialResponse -import androidx.credentials.CreatePublicKeyCredentialRequest -import androidx.credentials.CreatePublicKeyCredentialResponse -import androidx.credentials.CredentialManager -import androidx.credentials.CredentialManagerCallback -import androidx.credentials.GetCredentialRequest -import androidx.credentials.GetCredentialResponse -import androidx.credentials.GetPublicKeyCredentialOption -import androidx.credentials.PublicKeyCredential -import androidx.credentials.exceptions.CreateCredentialCancellationException -import androidx.credentials.exceptions.CreateCredentialException -import androidx.credentials.exceptions.CreateCredentialInterruptedException -import androidx.credentials.exceptions.CreateCredentialProviderConfigurationException -import androidx.credentials.exceptions.GetCredentialCancellationException -import androidx.credentials.exceptions.GetCredentialException -import androidx.credentials.exceptions.GetCredentialInterruptedException -import androidx.credentials.exceptions.GetCredentialUnsupportedException -import androidx.credentials.exceptions.NoCredentialException -import com.auth0.android.authentication.AuthenticationAPIClient -import com.auth0.android.authentication.AuthenticationException -import com.auth0.android.authentication.ParameterBuilder -import com.auth0.android.callback.Callback -import com.auth0.android.request.PublicKeyCredentialResponse -import com.auth0.android.request.UserMetadataRequest -import com.auth0.android.result.Credentials -import com.auth0.android.result.PasskeyChallengeResponse -import com.auth0.android.result.PasskeyRegistrationResponse -import com.google.gson.Gson -import java.util.concurrent.Executor -import java.util.concurrent.Executors - - -internal class PasskeyManager( - private val authenticationAPIClient: AuthenticationAPIClient, - private val credentialManager: CredentialManager -) { - - private val TAG = PasskeyManager::class.simpleName - - @RequiresApi(api = Build.VERSION_CODES.P) - @SuppressLint("PublicKeyCredential") - fun signup( - context: Context, - userMetadata: UserMetadataRequest, - parameters: Map, - callback: Callback, - executor: Executor = Executors.newSingleThreadExecutor() - ) { - - authenticationAPIClient.signupWithPasskey(userMetadata, parameters) - .start(object : Callback { - override fun onSuccess(result: PasskeyRegistrationResponse) { - val pasKeyRegistrationResponse = result - val request = CreatePublicKeyCredentialRequest( - Gson().toJson( - pasKeyRegistrationResponse.authParamsPublicKey - ) - ) - var response: CreatePublicKeyCredentialResponse? - - credentialManager.createCredentialAsync(context, - request, - CancellationSignal(), - executor, - object : - CredentialManagerCallback { - - override fun onError(e: CreateCredentialException) { - Log.w(TAG, "Error while creating passkey") - callback.onFailure(handleCreationFailure(e)) - } - - override fun onResult(result: CreateCredentialResponse) { - - response = result as CreatePublicKeyCredentialResponse - val authRequest = Gson().fromJson( - response?.registrationResponseJson, - PublicKeyCredentialResponse::class.java - ) - authenticationAPIClient.signinWithPasskey( - pasKeyRegistrationResponse.authSession, authRequest, parameters - ) - .validateClaims() - .start(callback) - } - }) - - } - - override fun onFailure(error: AuthenticationException) { - callback.onFailure(error) - } - }) - - } - - - @RequiresApi(api = Build.VERSION_CODES.P) - fun signin( - context: Context, - parameters: Map, - callback: Callback, - executor: Executor = Executors.newSingleThreadExecutor() - ) { - authenticationAPIClient.passkeyChallenge(parameters[ParameterBuilder.REALM_KEY]) - .start(object : Callback { - override fun onSuccess(result: PasskeyChallengeResponse) { - val passkeyChallengeResponse = result - val request = - GetPublicKeyCredentialOption(Gson().toJson(passkeyChallengeResponse.authParamsPublicKey)) - val getCredRequest = GetCredentialRequest( - listOf(request) - ) - credentialManager.getCredentialAsync(context, - getCredRequest, - CancellationSignal(), - executor, - object : - CredentialManagerCallback { - override fun onError(e: GetCredentialException) { - Log.w(TAG, "Error while fetching public key credential") - callback.onFailure(handleGetCredentialFailure(e)) - } - - override fun onResult(result: GetCredentialResponse) { - when (val credential = result.credential) { - is PublicKeyCredential -> { - val authRequest = Gson().fromJson( - credential.authenticationResponseJson, - PublicKeyCredentialResponse::class.java - ) - authenticationAPIClient.signinWithPasskey( - passkeyChallengeResponse.authSession, - authRequest, - parameters - ) - .validateClaims() - .start(callback) - } - - else -> { - Log.w( - TAG, - "Received unrecognized credential type ${credential.type}.This shouldn't happen" - ) - callback.onFailure(AuthenticationException("Received unrecognized credential type ${credential.type}")) - } - } - } - }) - - } - - override fun onFailure(error: AuthenticationException) { - callback.onFailure(error) - } - }) - - } - - private fun handleCreationFailure(exception: CreateCredentialException): AuthenticationException { - return when (exception) { - - is CreateCredentialCancellationException -> { - AuthenticationException( - AuthenticationException.ERROR_VALUE_AUTHENTICATION_CANCELED, - "The user cancelled passkey authentication operation." - ) - } - - is CreateCredentialInterruptedException -> { - AuthenticationException( - "Passkey authentication was interrupted. Please retry the call." - ) - } - - is CreateCredentialProviderConfigurationException -> { - AuthenticationException( - "Provider configuration dependency is missing. Ensure credentials-play-services-auth dependency is added." - ) - } - - else -> { - Log.w(TAG, "Unexpected exception type ${exception::class.java.name}") - AuthenticationException( - "An error occurred when trying to authenticate with passkey" - ) - } - } - } - - private fun handleGetCredentialFailure(exception: GetCredentialException): AuthenticationException { - - return when (exception) { - is GetCredentialCancellationException -> { - AuthenticationException( - AuthenticationException.ERROR_VALUE_AUTHENTICATION_CANCELED, - "The user cancelled passkey authentication operation." - ) - } - - is GetCredentialInterruptedException -> { - AuthenticationException( - "Passkey authentication was interrupted. Please retry the call." - ) - } - - is GetCredentialUnsupportedException -> { - AuthenticationException( - "Credential manager is unsupported. Please update the device." - ) - } - - - is NoCredentialException -> { - AuthenticationException( - "No viable credential is available for the user" - ) - } - - - else -> { - Log.w(TAG, "Unexpected exception type ${exception::class.java.name}") - AuthenticationException( - "An error occurred when trying to authenticate with passkey" - ) - } - } - } - -} \ No newline at end of file diff --git a/auth0/src/main/java/com/auth0/android/provider/WebAuthProvider.kt b/auth0/src/main/java/com/auth0/android/provider/WebAuthProvider.kt index 83ecbc6ed..9224d9719 100644 --- a/auth0/src/main/java/com/auth0/android/provider/WebAuthProvider.kt +++ b/auth0/src/main/java/com/auth0/android/provider/WebAuthProvider.kt @@ -8,15 +8,15 @@ import androidx.annotation.VisibleForTesting import com.auth0.android.Auth0 import com.auth0.android.annotation.ExperimentalAuth0Api import com.auth0.android.authentication.AuthenticationException +import com.auth0.android.authentication.storage.CredentialsManagerException import com.auth0.android.callback.Callback import com.auth0.android.result.Credentials -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withContext -import java.util.Locale +import kotlinx.coroutines.* +import java.util.* import kotlin.coroutines.CoroutineContext import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException +import kotlin.jvm.Throws /** * OAuth2 Web Authentication Provider. diff --git a/auth0/src/main/java/com/auth0/android/request/PublicKeyCredentialRequest.kt b/auth0/src/main/java/com/auth0/android/request/PublicKeyCredentialRequest.kt deleted file mode 100644 index ddedf864c..000000000 --- a/auth0/src/main/java/com/auth0/android/request/PublicKeyCredentialRequest.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.auth0.android.request - - -import com.google.gson.annotations.SerializedName - -internal data class PublicKeyCredentialResponse( - @SerializedName("authenticatorAttachment") - val authenticatorAttachment: String, - @SerializedName("clientExtensionResults") - val clientExtensionResults: ClientExtensionResults, - @SerializedName("id") - val id: String, - @SerializedName("rawId") - val rawId: String, - @SerializedName("response") - val response: Response, - @SerializedName("type") - val type: String -) - - -public data class Response( - @SerializedName("attestationObject") - val attestationObject: String, - @SerializedName("authenticatorData") - val authenticatorData: String, - @SerializedName("clientDataJSON") - val clientDataJSON: String, - @SerializedName("transports") - val transports: List, - @SerializedName("signature") - val signature:String, - @SerializedName("userHandle") - val userHandle:String -) - -public data class CredProps( - @SerializedName("rk") - val rk: Boolean -) - -public data class ClientExtensionResults( - @SerializedName("credProps") - val credProps: CredProps -) \ No newline at end of file diff --git a/auth0/src/main/java/com/auth0/android/request/Request.kt b/auth0/src/main/java/com/auth0/android/request/Request.kt index 287c38e99..e6825e9f5 100755 --- a/auth0/src/main/java/com/auth0/android/request/Request.kt +++ b/auth0/src/main/java/com/auth0/android/request/Request.kt @@ -56,18 +56,6 @@ public interface Request { */ public fun addParameter(name: String, value: String): Request - - /** - * Add parameter of [Any] type to the request with a given name - * - * @param name of the parameter - * @param value of the parameter - * @return itself - */ - public fun addParameter(name: String,value:Any):Request { - return this - } - /** * Adds an additional header for the request * diff --git a/auth0/src/main/java/com/auth0/android/request/UserMetadataRequest.kt b/auth0/src/main/java/com/auth0/android/request/UserMetadataRequest.kt deleted file mode 100644 index 2a1f808c9..000000000 --- a/auth0/src/main/java/com/auth0/android/request/UserMetadataRequest.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.auth0.android.request - -import com.google.gson.annotations.SerializedName - -/** - * User metadata request used in Passkey authentication - */ -internal data class UserMetadataRequest( - @field:SerializedName("email") val email: String? = null, - @field:SerializedName("phone_number") val phoneNumber: String? = null, - @field:SerializedName("username") val userName: String? = null, - @field:SerializedName("name") val name: String? = null, -) \ No newline at end of file diff --git a/auth0/src/main/java/com/auth0/android/request/internal/BaseAuthenticationRequest.kt b/auth0/src/main/java/com/auth0/android/request/internal/BaseAuthenticationRequest.kt index edd7fcebd..e9a9c0b91 100755 --- a/auth0/src/main/java/com/auth0/android/request/internal/BaseAuthenticationRequest.kt +++ b/auth0/src/main/java/com/auth0/android/request/internal/BaseAuthenticationRequest.kt @@ -17,8 +17,7 @@ import java.util.* internal open class BaseAuthenticationRequest( private val request: Request, - private val clientId: String, baseURL: String -) : AuthenticationRequest { + private val clientId: String, baseURL: String) : AuthenticationRequest { private companion object { private val TAG = BaseAuthenticationRequest::class.java.simpleName @@ -29,10 +28,8 @@ internal open class BaseAuthenticationRequest( @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal var validateClaims = false - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal var idTokenVerificationLeeway: Int? = null - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal var idTokenVerificationIssuer: String = baseURL @@ -124,11 +121,6 @@ internal open class BaseAuthenticationRequest( return this } - override fun addParameter(name: String, value: Any): AuthenticationRequest { - request.addParameter(name, value) - return this - } - override fun addHeader(name: String, value: String): AuthenticationRequest { request.addHeader(name, value) return this @@ -138,7 +130,7 @@ internal open class BaseAuthenticationRequest( warnClaimValidation() request.start(object : Callback { override fun onSuccess(result: Credentials) { - if (validateClaims) { + if(validateClaims) { try { verifyClaims(result.idToken) } catch (e: AuthenticationException) { @@ -159,7 +151,7 @@ internal open class BaseAuthenticationRequest( override fun execute(): Credentials { warnClaimValidation() val credentials = request.execute() - if (validateClaims) { + if(validateClaims) { verifyClaims(credentials.idToken) } return credentials @@ -170,7 +162,7 @@ internal open class BaseAuthenticationRequest( override suspend fun await(): Credentials { warnClaimValidation() val credentials = request.await() - if (validateClaims) { + if(validateClaims) { verifyClaims(credentials.idToken) } return credentials @@ -207,11 +199,8 @@ internal open class BaseAuthenticationRequest( } private fun warnClaimValidation() { - if (!validateClaims) { - Log.e( - TAG, - "The request is made without validating claims. Enable claim validation by calling AuthenticationRequest#validateClaims()" - ) + if(!validateClaims) { + Log.e(TAG, "The request is made without validating claims. Enable claim validation by calling AuthenticationRequest#validateClaims()") } } } \ No newline at end of file diff --git a/auth0/src/main/java/com/auth0/android/request/internal/BaseRequest.kt b/auth0/src/main/java/com/auth0/android/request/internal/BaseRequest.kt index d4f72025a..f0671af19 100755 --- a/auth0/src/main/java/com/auth0/android/request/internal/BaseRequest.kt +++ b/auth0/src/main/java/com/auth0/android/request/internal/BaseRequest.kt @@ -55,7 +55,7 @@ internal open class BaseRequest( return addParameter(name, anyValue) } - override fun addParameter(name: String, value: Any): Request { + internal fun addParameter(name: String, value: Any): Request { options.parameters[name] = value return this } diff --git a/auth0/src/main/java/com/auth0/android/result/PasskeyChallengeResponse.kt b/auth0/src/main/java/com/auth0/android/result/PasskeyChallengeResponse.kt deleted file mode 100644 index 8c49afde1..000000000 --- a/auth0/src/main/java/com/auth0/android/result/PasskeyChallengeResponse.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.auth0.android.result - - -import com.google.gson.annotations.SerializedName - -internal data class PasskeyChallengeResponse( - @SerializedName("auth_session") - val authSession: String, - @SerializedName("authn_params_public_key") - val authParamsPublicKey: AuthParamsPublicKey -) - -internal data class AuthParamsPublicKey( - @SerializedName("challenge") - val challenge: String, - @SerializedName("rpId") - val rpId: String, - @SerializedName("timeout") - val timeout: Int, - @SerializedName("userVerification") - val userVerification: String -) \ No newline at end of file diff --git a/auth0/src/main/java/com/auth0/android/result/PasskeyRegistrationResponse.kt b/auth0/src/main/java/com/auth0/android/result/PasskeyRegistrationResponse.kt deleted file mode 100644 index 42f778e75..000000000 --- a/auth0/src/main/java/com/auth0/android/result/PasskeyRegistrationResponse.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.auth0.android.result - - -import com.google.gson.annotations.SerializedName - -internal data class PasskeyRegistrationResponse( - @SerializedName("auth_session") - val authSession: String, - @SerializedName("authn_params_public_key") - val authParamsPublicKey: AuthnParamsPublicKey -) - -internal data class AuthnParamsPublicKey( - @SerializedName("authenticatorSelection") - val authenticatorSelection: AuthenticatorSelection, - @SerializedName("challenge") - val challenge: String, - @SerializedName("pubKeyCredParams") - val pubKeyCredParams: List, - @SerializedName("rp") - val relyingParty: RelyingParty, - @SerializedName("timeout") - val timeout: Long, - @SerializedName("user") - val user: PasskeyUser -) - -internal data class AuthenticatorSelection( - @SerializedName("residentKey") - val residentKey: String, - @SerializedName("userVerification") - val userVerification: String -) - -internal data class PubKeyCredParam( - @SerializedName("alg") - val alg: Int, - @SerializedName("type") - val type: String -) - -internal data class RelyingParty( - @SerializedName("id") - val id: String, - @SerializedName("name") - val name: String -) - -internal data class PasskeyUser( - @SerializedName("displayName") - val displayName: String, - @SerializedName("id") - val id: String, - @SerializedName("name") - val name: String -) \ No newline at end of file diff --git a/auth0/src/test/java/com/auth0/android/authentication/AuthenticationAPIClientTest.kt b/auth0/src/test/java/com/auth0/android/authentication/AuthenticationAPIClientTest.kt index 5830ba43a..8212c2184 100755 --- a/auth0/src/test/java/com/auth0/android/authentication/AuthenticationAPIClientTest.kt +++ b/auth0/src/test/java/com/auth0/android/authentication/AuthenticationAPIClientTest.kt @@ -9,31 +9,18 @@ import com.auth0.android.request.HttpMethod import com.auth0.android.request.NetworkingClient import com.auth0.android.request.RequestOptions import com.auth0.android.request.ServerResponse -import com.auth0.android.request.UserMetadataRequest import com.auth0.android.request.internal.RequestFactory import com.auth0.android.request.internal.ThreadSwitcherShadow -import com.auth0.android.result.Authentication -import com.auth0.android.result.Challenge -import com.auth0.android.result.Credentials -import com.auth0.android.result.DatabaseUser -import com.auth0.android.result.PasskeyRegistrationResponse -import com.auth0.android.result.UserProfile +import com.auth0.android.result.* import com.auth0.android.util.Auth0UserAgent import com.auth0.android.util.AuthenticationAPIMockServer -import com.auth0.android.util.AuthenticationAPIMockServer.Companion.SESSION_ID import com.auth0.android.util.AuthenticationCallbackMatcher import com.auth0.android.util.MockAuthenticationCallback import com.auth0.android.util.SSLTestUtils.testClient import com.google.gson.Gson import com.google.gson.GsonBuilder -import com.google.gson.JsonElement import com.google.gson.reflect.TypeToken -import com.nhaarman.mockitokotlin2.any -import com.nhaarman.mockitokotlin2.argumentCaptor -import com.nhaarman.mockitokotlin2.eq -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.verify -import com.nhaarman.mockitokotlin2.whenever +import com.nhaarman.mockitokotlin2.* import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import okhttp3.HttpUrl.Companion.toHttpUrlOrNull @@ -52,7 +39,7 @@ import java.io.ByteArrayInputStream import java.io.FileReader import java.io.InputStream import java.security.PublicKey -import java.util.Locale +import java.util.* @RunWith(RobolectricTestRunner::class) @Config(shadows = [ThreadSwitcherShadow::class]) @@ -188,84 +175,6 @@ public class AuthenticationAPIClientTest { assertThat(body, Matchers.not(Matchers.hasKey("connection"))) } - @Test - public fun shouldSigninWithPasskey() { - mockAPI.willReturnSuccessfulLogin() - val callback = MockAuthenticationCallback() - val auth0 = auth0 - val client = AuthenticationAPIClient(auth0) - client.signinWithPasskey("auth-session", mock(), emptyMap()) - .start(callback) - ShadowLooper.idleMainLooper() - assertThat( - callback, AuthenticationCallbackMatcher.hasPayloadOfType( - Credentials::class.java - ) - ) - val request = mockAPI.takeRequest() - assertThat( - request.getHeader("Accept-Language"), Matchers.`is`( - defaultLocale - ) - ) - val body = bodyFromRequest(request) - assertThat(request.path, Matchers.equalTo("/oauth/token")) - assertThat(body, Matchers.hasEntry("client_id", CLIENT_ID)) - assertThat( - body, - Matchers.hasEntry("grant_type", "urn:okta:params:oauth:grant-type:webauthn") - ) - assertThat(body, Matchers.hasKey("authn_response")) - assertThat(body, Matchers.hasEntry("auth_session", "auth-session")) - } - - @Test - public fun shouldSignupWithPasskey() { - mockAPI.willReturnSuccessfulPasskeyRegistration() - val auth0 = auth0 - val client = AuthenticationAPIClient(auth0) - val registrationResponse = client.signupWithPasskey( - mock(), - mapOf("realm" to MY_CONNECTION) - ) - .execute() - val request = mockAPI.takeRequest() - assertThat( - request.getHeader("Accept-Language"), Matchers.`is`( - defaultLocale - ) - ) - val body = bodyFromRequest(request) - assertThat(request.path, Matchers.equalTo("/passkey/register")) - assertThat(body, Matchers.hasEntry("client_id", CLIENT_ID)) - assertThat(body, Matchers.hasEntry("realm", MY_CONNECTION)) - assertThat(body, Matchers.hasKey("user_profile")) - assertThat(registrationResponse, Matchers.`is`(Matchers.notNullValue())) - assertThat(registrationResponse.authSession, Matchers.comparesEqualTo(SESSION_ID)) - } - - @Test - public fun shouldGetPasskeyChallenge() { - mockAPI.willReturnSuccessfulPasskeyChallenge() - val auth0 = auth0 - val client = AuthenticationAPIClient(auth0) - val challengeResponse = client.passkeyChallenge(MY_CONNECTION) - .execute() - val request = mockAPI.takeRequest() - assertThat( - request.getHeader("Accept-Language"), Matchers.`is`( - defaultLocale - ) - ) - val body = bodyFromRequest(request) - assertThat(request.path, Matchers.equalTo("/passkey/challenge")) - assertThat(body, Matchers.hasEntry("client_id", CLIENT_ID)) - assertThat(body, Matchers.hasEntry("realm", MY_CONNECTION)) - assertThat(challengeResponse, Matchers.`is`(Matchers.notNullValue())) - assertThat(challengeResponse.authSession, Matchers.comparesEqualTo(SESSION_ID)) - - } - @Test public fun shouldLoginWithMFARecoveryCode() { mockAPI.willReturnSuccessfulLoginWithRecoveryCode() diff --git a/auth0/src/test/java/com/auth0/android/authentication/request/AuthenticationRequestMock.java b/auth0/src/test/java/com/auth0/android/authentication/request/AuthenticationRequestMock.java index 4b5d6d0e2..f79dce5ca 100644 --- a/auth0/src/test/java/com/auth0/android/authentication/request/AuthenticationRequestMock.java +++ b/auth0/src/test/java/com/auth0/android/authentication/request/AuthenticationRequestMock.java @@ -6,7 +6,6 @@ import com.auth0.android.authentication.AuthenticationException; import com.auth0.android.callback.Callback; import com.auth0.android.request.AuthenticationRequest; -import com.auth0.android.request.Request; import com.auth0.android.result.Credentials; import java.util.Map; @@ -49,12 +48,6 @@ public AuthenticationRequest addParameter(@NonNull String name, @NonNull String return this; } - @NonNull - @Override - public Request addParameter(@NonNull String name, @NonNull Object value) { - return this; - } - @NonNull @Override public AuthenticationRequest addHeader(@NonNull String name, @NonNull String value) { diff --git a/auth0/src/test/java/com/auth0/android/authentication/request/RequestMock.java b/auth0/src/test/java/com/auth0/android/authentication/request/RequestMock.java index 69f77d67c..b2748c54f 100644 --- a/auth0/src/test/java/com/auth0/android/authentication/request/RequestMock.java +++ b/auth0/src/test/java/com/auth0/android/authentication/request/RequestMock.java @@ -55,10 +55,4 @@ public void start(@NonNull Callback callback) { public T execute() throws Auth0Exception { return null; } - - @NonNull - @Override - public Request addParameter(@NonNull String name, @NonNull Object value) { - return this; - } } diff --git a/auth0/src/test/java/com/auth0/android/provider/PasskeyManagerTest.kt b/auth0/src/test/java/com/auth0/android/provider/PasskeyManagerTest.kt deleted file mode 100644 index a5f53f744..000000000 --- a/auth0/src/test/java/com/auth0/android/provider/PasskeyManagerTest.kt +++ /dev/null @@ -1,362 +0,0 @@ -package com.auth0.android.provider - -import android.content.Context -import androidx.credentials.CreateCredentialResponse -import androidx.credentials.CreatePublicKeyCredentialResponse -import androidx.credentials.CredentialManager -import androidx.credentials.CredentialManagerCallback -import androidx.credentials.GetCredentialRequest -import androidx.credentials.GetCredentialResponse -import androidx.credentials.PublicKeyCredential -import androidx.credentials.exceptions.CreateCredentialException -import androidx.credentials.exceptions.CreateCredentialInterruptedException -import androidx.credentials.exceptions.GetCredentialException -import androidx.credentials.exceptions.GetCredentialInterruptedException -import com.auth0.android.authentication.AuthenticationAPIClient -import com.auth0.android.authentication.AuthenticationException -import com.auth0.android.authentication.request.AuthenticationRequestMock -import com.auth0.android.authentication.request.RequestMock -import com.auth0.android.callback.Callback -import com.auth0.android.request.UserMetadataRequest -import com.auth0.android.result.AuthParamsPublicKey -import com.auth0.android.result.AuthenticatorSelection -import com.auth0.android.result.AuthnParamsPublicKey -import com.auth0.android.result.Credentials -import com.auth0.android.result.PasskeyChallengeResponse -import com.auth0.android.result.PasskeyRegistrationResponse -import com.auth0.android.result.PasskeyUser -import com.auth0.android.result.PubKeyCredParam -import com.auth0.android.result.RelyingParty -import com.nhaarman.mockitokotlin2.KArgumentCaptor -import com.nhaarman.mockitokotlin2.any -import com.nhaarman.mockitokotlin2.argumentCaptor -import com.nhaarman.mockitokotlin2.doAnswer -import com.nhaarman.mockitokotlin2.eq -import com.nhaarman.mockitokotlin2.mock -import com.nhaarman.mockitokotlin2.never -import com.nhaarman.mockitokotlin2.verify -import com.nhaarman.mockitokotlin2.whenever -import org.junit.Assert -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito.`when` -import org.mockito.MockitoAnnotations -import org.robolectric.RobolectricTestRunner -import java.util.Date -import java.util.concurrent.Executor - - -@RunWith(RobolectricTestRunner::class) -public class PasskeyManagerTest { - - private lateinit var passkeyManager: PasskeyManager - - @Mock - private lateinit var callback: Callback - - @Mock - private lateinit var authenticationAPIClient: AuthenticationAPIClient - - @Mock - private lateinit var credentialManager: CredentialManager - - @Mock - private lateinit var context: Context - - private val serialExecutor = Executor { runnable -> runnable.run() } - - private val credentialsCaptor: KArgumentCaptor = argumentCaptor() - private val exceptionCaptor: KArgumentCaptor = argumentCaptor() - - - private val passkeyRegistrationResponse = PasskeyRegistrationResponse( - authSession = "dummyAuthSession", - authParamsPublicKey = AuthnParamsPublicKey( - authenticatorSelection = AuthenticatorSelection( - residentKey = "required", - userVerification = "preferred" - ), - challenge = "dummyChallenge", - pubKeyCredParams = listOf( - PubKeyCredParam( - alg = -7, - type = "public-key" - ) - ), - relyingParty = RelyingParty( - id = "dummyRpId", - name = "dummyRpName" - ), - timeout = 60000L, - user = PasskeyUser( - displayName = "displayName", - id = "userId", - name = "userName" - ) - ) - ) - - private val registrationResponseJSON = """ - { - "id": "id", - "rawId": "rawId", - "response": { - "attestationObject": "attnObject", - "clientDataJSON": "dataJSON" - }, - "type": "public-key" - } - """ - - private val passkeyChallengeResponse = PasskeyChallengeResponse( - authSession = "authSession", - authParamsPublicKey = AuthParamsPublicKey( - challenge = "challenge", - rpId = "RpId", - timeout = 60000, - userVerification = "preferred" - ) - ) - - @Before - public fun setUp() { - MockitoAnnotations.openMocks(this) - passkeyManager = PasskeyManager(authenticationAPIClient, credentialManager) - } - - - @Test - public fun shouldSignUpWithPasskeySuccess() { - val userMetadata: UserMetadataRequest = mock() - val parameters = mapOf("realm" to "testRealm") - - `when`(authenticationAPIClient.signupWithPasskey(userMetadata, parameters)).thenReturn( - RequestMock(passkeyRegistrationResponse, null) - ) - `when`(authenticationAPIClient.signinWithPasskey(any(), any(), any())).thenReturn( - AuthenticationRequestMock( - Credentials( - "expectedIdToken", - "codeAccess", - "codeType", - "codeRefresh", - Date(), - "codeScope" - ), null - ) - ) - - val createResponse: CreatePublicKeyCredentialResponse = mock() - `when`(createResponse.registrationResponseJson).thenReturn( - registrationResponseJSON - ) - - whenever( - credentialManager.createCredentialAsync( - any(), - any(), - any(), - any(), - any() - ) - ).thenAnswer { - (it.arguments[4] as CredentialManagerCallback).onResult( - createResponse - ) - } - - passkeyManager.signup(context, userMetadata, parameters, callback, serialExecutor) - - verify(authenticationAPIClient).signupWithPasskey(userMetadata, parameters) - verify(credentialManager).createCredentialAsync(eq(context), any(), any(), any(), any()) - verify(authenticationAPIClient).signinWithPasskey(any(), any(), any()) - verify(callback).onSuccess(credentialsCaptor.capture()) - Assert.assertEquals("codeAccess", credentialsCaptor.firstValue.accessToken) - Assert.assertEquals("codeScope", credentialsCaptor.firstValue.scope) - - } - - @Test - public fun shouldSignUpWithPasskeyApiFailure() { - val userMetadata: UserMetadataRequest = mock() - val parameters = mapOf("realm" to "testRealm") - val error = AuthenticationException("Signup failed") - `when`( - authenticationAPIClient.signupWithPasskey( - userMetadata, - parameters - ) - ).thenReturn(RequestMock(null, error)) - passkeyManager.signup(context, userMetadata, parameters, callback, serialExecutor) - verify(authenticationAPIClient).signupWithPasskey(userMetadata, parameters) - verify(authenticationAPIClient, never()).signinWithPasskey(any(), any(), any()) - verify(credentialManager, never()).createCredentialAsync( - any(), - any(), - any(), - any(), - any() - ) - verify(callback).onFailure(error) - } - - @Test - public fun shouldSignUpWithPasskeyCreateCredentialFailure() { - val userMetadata: UserMetadataRequest = mock() - val parameters = mapOf("realm" to "testRealm") - `when`( - authenticationAPIClient.signupWithPasskey( - userMetadata, - parameters - ) - ).thenReturn(RequestMock(passkeyRegistrationResponse, null)) - - whenever( - credentialManager.createCredentialAsync( - any(), - any(), - any(), - any(), - any() - ) - ).thenAnswer { - (it.arguments[4] as CredentialManagerCallback).onError( - CreateCredentialInterruptedException() - ) - } - - passkeyManager.signup(context, userMetadata, parameters, callback, serialExecutor) - verify(authenticationAPIClient).signupWithPasskey(userMetadata, parameters) - verify(credentialManager).createCredentialAsync(eq(context), any(), any(), any(), any()) - verify(authenticationAPIClient, never()).signinWithPasskey(any(), any(), any()) - verify(callback).onFailure(exceptionCaptor.capture()) - Assert.assertEquals( - AuthenticationException::class.java, - exceptionCaptor.firstValue.javaClass - ) - Assert.assertEquals( - "Passkey authentication was interrupted. Please retry the call.", - exceptionCaptor.firstValue.message - ) - } - - - @Test - public fun shouldSignInWithPasskeySuccess() { - val parameters = mapOf("realm" to "testRealm") - val credentialResponse: GetCredentialResponse = mock() - - `when`(authenticationAPIClient.passkeyChallenge(parameters["realm"])).thenReturn( - RequestMock(passkeyChallengeResponse, null) - ) - - `when`(credentialResponse.credential).thenReturn( - PublicKeyCredential(registrationResponseJSON) - ) - - `when`(authenticationAPIClient.signinWithPasskey(any(), any(), any())).thenReturn( - AuthenticationRequestMock( - Credentials( - "expectedIdToken", - "codeAccess", - "codeType", - "codeRefresh", - Date(), - "codeScope" - ), null - ) - ) - - doAnswer { - val callback = - it.getArgument>( - 4 - ) - callback.onResult(credentialResponse) - }.`when`(credentialManager) - .getCredentialAsync(any(), any(), any(), any(), any()) - - passkeyManager.signin(context, parameters, callback, serialExecutor) - - verify(authenticationAPIClient).passkeyChallenge(parameters["realm"]) - verify(credentialManager).getCredentialAsync( - any(), - any(), - any(), - any(), - any() - ) - verify(authenticationAPIClient).signinWithPasskey(any(), any(), any()) - verify(callback).onSuccess(credentialsCaptor.capture()) - Assert.assertEquals("codeAccess", credentialsCaptor.firstValue.accessToken) - Assert.assertEquals("codeScope", credentialsCaptor.firstValue.scope) - } - - - @Test - public fun shouldSignInWithPasskeyApiFailure() { - val parameters = mapOf("realm" to "testRealm") - val error = AuthenticationException("Signin failed") - - `when`(authenticationAPIClient.passkeyChallenge(parameters["realm"])).thenReturn( - RequestMock(null, error) - ) - - passkeyManager.signin(context, parameters, callback, serialExecutor) - - verify(authenticationAPIClient).passkeyChallenge(any()) - verify(credentialManager, never()).getCredentialAsync( - any(), - any(), - any(), - any(), - any() - ) - verify(authenticationAPIClient, never()).signinWithPasskey(any(), any(), any()) - verify(callback).onFailure(error) - } - - @Test - public fun shouldSignInWithPasskeyGetCredentialFailure() { - val parameters = mapOf("realm" to "testRealm") - `when`(authenticationAPIClient.passkeyChallenge(parameters["realm"])).thenReturn( - RequestMock(passkeyChallengeResponse, null) - ) - - whenever( - credentialManager.getCredentialAsync( - any(), - any(), - any(), - any(), - any() - ) - ).thenAnswer { - (it.arguments[4] as CredentialManagerCallback).onError( - GetCredentialInterruptedException() - ) - } - - passkeyManager.signin(context, parameters, callback, serialExecutor) - verify(authenticationAPIClient).passkeyChallenge(parameters["realm"]) - verify(credentialManager).getCredentialAsync( - any(), - any(), - any(), - any(), - any() - ) - verify(authenticationAPIClient, never()).signinWithPasskey(any(), any(), any()) - verify(callback).onFailure(exceptionCaptor.capture()) - Assert.assertEquals( - AuthenticationException::class.java, - exceptionCaptor.firstValue.javaClass - ) - Assert.assertEquals( - "Passkey authentication was interrupted. Please retry the call.", - exceptionCaptor.firstValue.message - ) - } -} diff --git a/auth0/src/test/java/com/auth0/android/util/AuthenticationAPIMockServer.kt b/auth0/src/test/java/com/auth0/android/util/AuthenticationAPIMockServer.kt index b95822ed7..b61a6c8ce 100755 --- a/auth0/src/test/java/com/auth0/android/util/AuthenticationAPIMockServer.kt +++ b/auth0/src/test/java/com/auth0/android/util/AuthenticationAPIMockServer.kt @@ -47,55 +47,6 @@ internal class AuthenticationAPIMockServer : APIMockServer() { return this } - fun willReturnSuccessfulPasskeyRegistration(): AuthenticationAPIMockServer { - val json = """{ - "authn_params_public_key":{ - "challenge": "$CHALLENGE", - "timeout": 6046456, - "rp": { - "id": "auth0.passkey.com", - "name": "Passkey Test" - }, - "pubKeyCredParams": [ - { - "type": "public-key", - "alg": -7 - }, - { - "type": "public-key", - "alg": -257 - } - ], - "authenticatorSelection": { - "authenticatorAttachment": "platform", - "residentKey": "required" - }, - "user": { - "id": "53b995f8bce68d9fc900099c", - "name": "p", - "displayName": "d" - } - }, - "auth_session": "$SESSION_ID" - }""" - server.enqueue(responseWithJSON(json, 200)) - return this - } - - fun willReturnSuccessfulPasskeyChallenge():AuthenticationAPIMockServer{ - val json = """{ - "authn_params_public_key":{ - "challenge": "$CHALLENGE", - "timeout": 604645, - "rpId": "domain", - "userVerification":"preferred" - }, - "auth_session": "$SESSION_ID" - }""" - server.enqueue(responseWithJSON(json, 200)) - return this - } - fun willReturnSuccessfulLoginWithRecoveryCode(): AuthenticationAPIMockServer { val json = """{ "refresh_token": "$REFRESH_TOKEN", @@ -204,8 +155,6 @@ internal class AuthenticationAPIMockServer : APIMockServer() { const val REFRESH_TOKEN = "REFRESH_TOKEN" const val ID_TOKEN = "ID_TOKEN" const val ACCESS_TOKEN = "ACCESS_TOKEN" - const val SESSION_ID = "SESSION_ID" private const val BEARER = "BEARER" - private const val CHALLENGE = "CHALLENGE" } } \ No newline at end of file diff --git a/proguard/proguard-jetpack.pro b/proguard/proguard-jetpack.pro deleted file mode 100644 index 6254a5381..000000000 --- a/proguard/proguard-jetpack.pro +++ /dev/null @@ -1,6 +0,0 @@ -# Jetpack libraries - --if class androidx.credentials.CredentialManager --keep class androidx.credentials.playservices.** { - *; -} \ No newline at end of file diff --git a/sample/build.gradle b/sample/build.gradle index 1abd84c39..5b3e0a66d 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -4,11 +4,11 @@ plugins { } android { - compileSdkVersion 34 + compileSdkVersion 31 defaultConfig { minSdkVersion 21 - targetSdkVersion 34 + targetSdkVersion 31 versionCode 1 versionName "1.0" diff --git a/sample/src/main/java/com/auth0/sample/DatabaseLoginFragment.kt b/sample/src/main/java/com/auth0/sample/DatabaseLoginFragment.kt index 4546908c9..08bf0ac54 100644 --- a/sample/src/main/java/com/auth0/sample/DatabaseLoginFragment.kt +++ b/sample/src/main/java/com/auth0/sample/DatabaseLoginFragment.kt @@ -17,7 +17,6 @@ import com.auth0.android.authentication.storage.SharedPreferencesStorage import com.auth0.android.callback.Callback import com.auth0.android.management.ManagementException import com.auth0.android.management.UsersAPIClient -import com.auth0.android.provider.PasskeyAuthProvider import com.auth0.android.provider.WebAuthProvider import com.auth0.android.request.DefaultClient import com.auth0.android.result.Credentials @@ -92,60 +91,6 @@ class DatabaseLoginFragment : Fragment() { dbLoginAsync(email, password) } } - - binding.btSignupPasskey.setOnClickListener { - PasskeyAuthProvider.signUp(account) - .setEmail("username@email.com") - .setRealm("Username-Password-Authentication") - .start( - requireActivity(), - object : Callback { - override fun onSuccess(result: Credentials) { - credentialsManager.saveCredentials(result) - Snackbar.make( - requireView(), - "Hello ${result.user.name}", - Snackbar.LENGTH_LONG - ).show() - } - - override fun onFailure(error: AuthenticationException) { - Snackbar.make( - requireView(), - error.getDescription(), - Snackbar.LENGTH_LONG - ) - .show() - } - }) - } - binding.btSignInPasskey.setOnClickListener { - PasskeyAuthProvider - .signIn(account) - .setRealm("Username-Password-Authentication") - .start(requireActivity(), object : Callback { - override fun onSuccess(result: Credentials) { - credentialsManager.saveCredentials(result) - Snackbar.make( - requireView(), - "Hello ${result.user.name}", - Snackbar.LENGTH_LONG - ).show() - } - - override fun onFailure(error: AuthenticationException) { - Snackbar.make( - requireView(), - error.getDescription(), - Snackbar.LENGTH_LONG - ) - .show() - } - - }) - - } - binding.btWebAuth.setOnClickListener { webAuth() } diff --git a/sample/src/main/res/layout/fragment_database_login.xml b/sample/src/main/res/layout/fragment_database_login.xml index 61d1a7bc3..af19b8b44 100644 --- a/sample/src/main/res/layout/fragment_database_login.xml +++ b/sample/src/main/res/layout/fragment_database_login.xml @@ -20,7 +20,6 @@ android:autofillHints="emailAddress" android:ems="10" android:hint="Email" - android:isCredential="true" android:inputType="textEmailAddress" android:text="asd@asd.asd" android:textSize="14sp" @@ -38,7 +37,6 @@ android:autofillHints="password" android:ems="10" android:hint="Password" - android:isCredential="true" android:inputType="textPassword" android:text="asdasd" android:textSize="14sp" @@ -69,45 +67,8 @@ android:textSize="16sp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/btSignInPasskey" /> - - -