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 f3a8cbd8..b11dc187 100755 --- a/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt @@ -8,10 +8,21 @@ import com.auth0.android.NetworkErrorException import com.auth0.android.dpop.DPoP import com.auth0.android.dpop.DPoPException import com.auth0.android.dpop.SenderConstraining -import com.auth0.android.request.* -import com.auth0.android.request.internal.* +import com.auth0.android.request.AuthenticationRequest +import com.auth0.android.request.ErrorAdapter +import com.auth0.android.request.JsonAdapter +import com.auth0.android.request.ProfileRequest +import com.auth0.android.request.PublicKeyCredentials +import com.auth0.android.request.Request +import com.auth0.android.request.SignUpRequest +import com.auth0.android.request.UserData +import com.auth0.android.request.internal.BaseAuthenticationRequest +import com.auth0.android.request.internal.BaseRequest +import com.auth0.android.request.internal.GsonAdapter import com.auth0.android.request.internal.GsonAdapter.Companion.forMap import com.auth0.android.request.internal.GsonAdapter.Companion.forMapOf +import com.auth0.android.request.internal.GsonProvider +import com.auth0.android.request.internal.RequestFactory import com.auth0.android.request.internal.ResponseUtils.isNetworkError import com.auth0.android.result.Challenge import com.auth0.android.result.Credentials @@ -749,13 +760,15 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe * * @param subjectTokenType the subject token type that is associated with the existing Identity Provider. e.g. 'http://acme.com/legacy-token' * @param subjectToken the subject token, typically obtained through the Identity Provider's SDK + * @param organization id of the organization the user belongs to * @return a request to configure and start that will yield [Credentials] */ public fun customTokenExchange( subjectTokenType: String, subjectToken: String, + organization: String? = null ): AuthenticationRequest { - return tokenExchange(subjectTokenType, subjectToken) + return tokenExchange(subjectTokenType, subjectToken, organization) } /** @@ -1043,13 +1056,17 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe */ private fun tokenExchange( subjectTokenType: String, - subjectToken: String + subjectToken: String, + organization: String? = null ): AuthenticationRequest { - val parameters = ParameterBuilder.newAuthenticationBuilder() - .setGrantType(ParameterBuilder.GRANT_TYPE_TOKEN_EXCHANGE) - .set(SUBJECT_TOKEN_TYPE_KEY, subjectTokenType) - .set(SUBJECT_TOKEN_KEY, subjectToken) - .asDictionary() + val parameters = ParameterBuilder.newAuthenticationBuilder().apply { + setGrantType(ParameterBuilder.GRANT_TYPE_TOKEN_EXCHANGE) + set(SUBJECT_TOKEN_TYPE_KEY, subjectTokenType) + set(SUBJECT_TOKEN_KEY, subjectToken) + organization?.let { + set(ORGANIZATION_KEY, it) + } + }.asDictionary() return loginWithToken(parameters) } diff --git a/auth0/src/main/java/com/auth0/android/request/AuthenticationRequest.kt b/auth0/src/main/java/com/auth0/android/request/AuthenticationRequest.kt index 0871967f..942a8172 100755 --- a/auth0/src/main/java/com/auth0/android/request/AuthenticationRequest.kt +++ b/auth0/src/main/java/com/auth0/android/request/AuthenticationRequest.kt @@ -2,7 +2,6 @@ package com.auth0.android.request import com.auth0.android.Auth0 import com.auth0.android.authentication.AuthenticationException -import com.auth0.android.callback.Callback import com.auth0.android.result.Credentials /** @@ -74,4 +73,15 @@ public interface AuthenticationRequest : Request { * @return itself */ public fun addHeader(name: String, value: String): Request + + /** + * Adds a validator to be executed before the request is sent. + * Multiple validators can be added and will be executed in order. + * + * @param validator the validator to add + * @return itself + */ + public fun addValidator(validator: RequestValidator): Request { + return this + } } \ No newline at end of file diff --git a/auth0/src/main/java/com/auth0/android/request/RequestValidator.kt b/auth0/src/main/java/com/auth0/android/request/RequestValidator.kt new file mode 100644 index 00000000..ab074b11 --- /dev/null +++ b/auth0/src/main/java/com/auth0/android/request/RequestValidator.kt @@ -0,0 +1,18 @@ +package com.auth0.android.request + +import com.auth0.android.Auth0Exception + +/** + * Interface for validating request parameters before execution. + * Validators are invoked before the network request is made. + */ +public interface RequestValidator { + + /** + * Validates the request options and parameters. + * @param options the request options to validate + * @throws Auth0Exception if validation fails + */ + @Throws(Auth0Exception::class) + public fun validate(options: RequestOptions) +} \ No newline at end of file diff --git a/auth0/src/main/java/com/auth0/android/request/SignUpRequest.kt b/auth0/src/main/java/com/auth0/android/request/SignUpRequest.kt index 285664ef..5f52bc6d 100755 --- a/auth0/src/main/java/com/auth0/android/request/SignUpRequest.kt +++ b/auth0/src/main/java/com/auth0/android/request/SignUpRequest.kt @@ -91,6 +91,11 @@ public class SignUpRequest return this } + override fun addValidator(validator: RequestValidator): AuthenticationRequest { + authenticationRequest.addValidator(validator) + return this + } + override fun withIdTokenVerificationLeeway(leeway: Int): SignUpRequest { authenticationRequest.withIdTokenVerificationLeeway(leeway) return this 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 a62716de..f8103304 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 @@ -7,11 +7,16 @@ import com.auth0.android.Auth0Exception import com.auth0.android.authentication.AuthenticationException import com.auth0.android.authentication.ParameterBuilder import com.auth0.android.callback.Callback -import com.auth0.android.provider.* +import com.auth0.android.provider.IdTokenMissingException +import com.auth0.android.provider.IdTokenVerificationOptions +import com.auth0.android.provider.IdTokenVerifier +import com.auth0.android.provider.TokenValidationException +import com.auth0.android.provider.UnexpectedIdTokenException import com.auth0.android.request.AuthenticationRequest import com.auth0.android.request.Request +import com.auth0.android.request.RequestValidator import com.auth0.android.result.Credentials -import java.util.* +import java.util.Date internal open class BaseAuthenticationRequest( private val request: Request, @@ -97,6 +102,11 @@ internal open class BaseAuthenticationRequest( return this } + override fun addValidator(validator: RequestValidator): AuthenticationRequest { + request.addValidator(validator) + return this + } + override fun validateClaims(): AuthenticationRequest { this.validateClaims = true return this 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 1f0c53e2..2d11052e 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 @@ -12,6 +12,7 @@ import com.auth0.android.request.JsonAdapter import com.auth0.android.request.NetworkingClient import com.auth0.android.request.Request import com.auth0.android.request.RequestOptions +import com.auth0.android.request.RequestValidator import com.auth0.android.request.ServerResponse import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers @@ -40,6 +41,8 @@ internal open class BaseRequest( private val options: RequestOptions = RequestOptions(method) + private val validators = mutableListOf() + override fun addHeader(name: String, value: String): Request { options.headers[name] = value return this @@ -70,6 +73,11 @@ internal open class BaseRequest( return this } + override fun addValidator(validator: RequestValidator): Request { + validators.add(validator) + return this + } + /** * Runs asynchronously and executes the network request, without blocking the current thread. * The result is parsed into a value and posted in the callback's onSuccess method or a @@ -126,6 +134,7 @@ internal open class BaseRequest( */ @kotlin.jvm.Throws(Auth0Exception::class) override fun execute(): T { + runClientValidation() val response: ServerResponse try { if (dPoP?.shouldGenerateProof(url, options.parameters) == true) { @@ -176,4 +185,10 @@ internal open class BaseRequest( } } + private fun runClientValidation() { + validators.forEach { validator -> + validator.validate(options) + } + } + } 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 b3e67098..bd6dc0a8 100755 --- a/auth0/src/test/java/com/auth0/android/authentication/AuthenticationAPIClientTest.kt +++ b/auth0/src/test/java/com/auth0/android/authentication/AuthenticationAPIClientTest.kt @@ -2298,7 +2298,7 @@ public class AuthenticationAPIClientTest { public fun shouldCustomTokenExchange() { mockAPI.willReturnSuccessfulLogin() val callback = MockAuthenticationCallback() - client.customTokenExchange("subject-token-type", "subject-token") + client.customTokenExchange("subject-token-type", "subject-token", "org_12345") .start(callback) ShadowLooper.idleMainLooper() val request = mockAPI.takeRequest() @@ -2316,6 +2316,7 @@ public class AuthenticationAPIClientTest { ) assertThat(body, Matchers.hasEntry("subject_token", "subject-token")) assertThat(body, Matchers.hasEntry("subject_token_type", "subject-token-type")) + assertThat(body, Matchers.hasEntry("organization", "org_12345")) assertThat(body, Matchers.hasEntry("scope", "openid profile email")) assertThat( callback, AuthenticationCallbackMatcher.hasPayloadOfType( @@ -2328,7 +2329,7 @@ public class AuthenticationAPIClientTest { public fun shouldCustomTokenExchangeSync() { mockAPI.willReturnSuccessfulLogin() val credentials = client - .customTokenExchange("subject-token-type", "subject-token") + .customTokenExchange("subject-token-type", "subject-token", "org_abc") .execute() val request = mockAPI.takeRequest() assertThat( @@ -2345,6 +2346,7 @@ public class AuthenticationAPIClientTest { ) assertThat(body, Matchers.hasEntry("subject_token", "subject-token")) assertThat(body, Matchers.hasEntry("subject_token_type", "subject-token-type")) + assertThat(body, Matchers.hasEntry("organization", "org_abc")) assertThat(body, Matchers.hasEntry("scope", "openid profile email")) assertThat(credentials, Matchers.`is`(Matchers.notNullValue())) } @@ -2371,6 +2373,7 @@ public class AuthenticationAPIClientTest { ) assertThat(body, Matchers.hasEntry("subject_token", "subject-token")) assertThat(body, Matchers.hasEntry("subject_token_type", "subject-token-type")) + assertThat(body, Matchers.not(Matchers.hasKey("organization"))) assertThat(body, Matchers.hasEntry("scope", "openid profile email")) assertThat(credentials, Matchers.`is`(Matchers.notNullValue())) } @@ -2871,7 +2874,10 @@ public class AuthenticationAPIClientTest { assertThat(request.path, Matchers.equalTo("/oauth/token")) val body = bodyFromRequest(request) - assertThat(body, Matchers.hasEntry("grant_type", ParameterBuilder.GRANT_TYPE_AUTHORIZATION_CODE)) + assertThat( + body, + Matchers.hasEntry("grant_type", ParameterBuilder.GRANT_TYPE_AUTHORIZATION_CODE) + ) assertThat(body, Matchers.hasEntry("code", "auth-code")) // Verify that key pair generation was attempted @@ -2935,7 +2941,10 @@ public class AuthenticationAPIClientTest { assertThat(request.path, Matchers.equalTo("/oauth/token")) val body = bodyFromRequest(request) - assertThat(body, Matchers.hasEntry("grant_type", ParameterBuilder.GRANT_TYPE_TOKEN_EXCHANGE)) + assertThat( + body, + Matchers.hasEntry("grant_type", ParameterBuilder.GRANT_TYPE_TOKEN_EXCHANGE) + ) assertThat(body, Matchers.hasEntry("subject_token_type", "subject-token-type")) // Verify that key pair generation was attempted @@ -3114,9 +3123,12 @@ public class AuthenticationAPIClientTest { client.useDPoP(mockContext).login(SUPPORT_AUTH0_COM, PASSWORD, MY_CONNECTION) .execute() } - Assert.assertEquals("Key pair is not found in the keystore. Please generate a key pair first.", exception.message) + Assert.assertEquals( + "Key pair is not found in the keystore. Please generate a key pair first.", + exception.message + ) assertThat(exception.cause, Matchers.notNullValue()) - assertThat(exception.cause, Matchers.instanceOf(DPoPException::class.java )) + assertThat(exception.cause, Matchers.instanceOf(DPoPException::class.java)) } private fun bodyFromRequest(request: RecordedRequest): Map { 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 4b5d6d0e..3c8d2381 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 @@ -7,8 +7,11 @@ import com.auth0.android.callback.Callback; import com.auth0.android.request.AuthenticationRequest; import com.auth0.android.request.Request; +import com.auth0.android.request.RequestValidator; import com.auth0.android.result.Credentials; +import org.jetbrains.annotations.NotNull; + import java.util.Map; public class AuthenticationRequestMock implements AuthenticationRequest { @@ -112,4 +115,9 @@ public AuthenticationRequest withIdTokenVerificationLeeway(int leeway) { public AuthenticationRequest withIdTokenVerificationIssuer(@NonNull String issuer) { return this; } + + @Override + public @NotNull AuthenticationRequest addValidator(@NotNull RequestValidator validator) { + return this; + } } 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 39404399..3cad906a 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 @@ -4,8 +4,10 @@ import com.auth0.android.Auth0Exception; import com.auth0.android.callback.Callback; -import com.auth0.android.request.HttpMethod; import com.auth0.android.request.Request; +import com.auth0.android.request.RequestValidator; + +import org.jetbrains.annotations.NotNull; import java.util.Map; @@ -62,4 +64,9 @@ public T execute() throws Auth0Exception { public Request addParameter(@NonNull String name, @NonNull Object value) { return this; } + + @Override + public @NotNull Request addValidator(@NotNull RequestValidator validator) { + return this; + } }