Skip to content

Commit 692d4be

Browse files
authored
fix : Added the missing user agent to MyAccount and MFAApiClient (#926)
1 parent f0ca8f6 commit 692d4be

File tree

4 files changed

+145
-36
lines changed

4 files changed

+145
-36
lines changed

auth0/src/main/java/com/auth0/android/authentication/mfa/MfaApiClient.kt

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import androidx.annotation.VisibleForTesting
44
import com.auth0.android.Auth0
55
import com.auth0.android.Auth0Exception
66
import com.auth0.android.authentication.ParameterBuilder
7-
import com.auth0.android.authentication.mfa.MfaException.*
7+
import com.auth0.android.authentication.mfa.MfaException.MfaChallengeException
8+
import com.auth0.android.authentication.mfa.MfaException.MfaEnrollmentException
9+
import com.auth0.android.authentication.mfa.MfaException.MfaListAuthenticatorsException
10+
import com.auth0.android.authentication.mfa.MfaException.MfaVerifyException
811
import com.auth0.android.request.ErrorAdapter
912
import com.auth0.android.request.JsonAdapter
1013
import com.auth0.android.request.Request
@@ -58,19 +61,27 @@ public class MfaApiClient @VisibleForTesting(otherwise = VisibleForTesting.PRIVA
5861

5962
// Specialized factories for MFA-specific errors
6063
private val listAuthenticatorsFactory: RequestFactory<MfaListAuthenticatorsException> by lazy {
61-
RequestFactory(auth0.networkingClient, createListAuthenticatorsErrorAdapter())
64+
RequestFactory(auth0.networkingClient, createListAuthenticatorsErrorAdapter()).apply {
65+
setAuth0ClientInfo(auth0.auth0UserAgent.value)
66+
}
6267
}
6368

6469
private val enrollmentFactory: RequestFactory<MfaEnrollmentException> by lazy {
65-
RequestFactory(auth0.networkingClient, createEnrollmentErrorAdapter())
70+
RequestFactory(auth0.networkingClient, createEnrollmentErrorAdapter()).apply {
71+
setAuth0ClientInfo(auth0.auth0UserAgent.value)
72+
}
6673
}
6774

6875
private val challengeFactory: RequestFactory<MfaChallengeException> by lazy {
69-
RequestFactory(auth0.networkingClient, createChallengeErrorAdapter())
76+
RequestFactory(auth0.networkingClient, createChallengeErrorAdapter()).apply {
77+
setAuth0ClientInfo(auth0.auth0UserAgent.value)
78+
}
7079
}
7180

7281
private val verifyFactory: RequestFactory<MfaVerifyException> by lazy {
73-
RequestFactory(auth0.networkingClient, createVerifyErrorAdapter())
82+
RequestFactory(auth0.networkingClient, createVerifyErrorAdapter()).apply {
83+
setAuth0ClientInfo(auth0.auth0UserAgent.value)
84+
}
7485
}
7586

7687
/**
@@ -175,7 +186,11 @@ public class MfaApiClient @VisibleForTesting(otherwise = VisibleForTesting.PRIVA
175186
*/
176187
public fun enroll(type: MfaEnrollmentType): Request<EnrollmentChallenge, MfaEnrollmentException> {
177188
return when (type) {
178-
is MfaEnrollmentType.Phone -> enrollOob(oobChannel = "sms", phoneNumber = type.phoneNumber)
189+
is MfaEnrollmentType.Phone -> enrollOob(
190+
oobChannel = "sms",
191+
phoneNumber = type.phoneNumber
192+
)
193+
179194
is MfaEnrollmentType.Email -> enrollOob(oobChannel = "email", email = type.email)
180195
is MfaEnrollmentType.Otp -> enrollOtpInternal()
181196
is MfaEnrollmentType.Push -> enrollOob(oobChannel = "auth0")
@@ -228,7 +243,6 @@ public class MfaApiClient @VisibleForTesting(otherwise = VisibleForTesting.PRIVA
228243
}
229244

230245

231-
232246
/**
233247
* Verifies an MFA challenge using the specified verification type.
234248
*
@@ -290,7 +304,7 @@ public class MfaApiClient @VisibleForTesting(otherwise = VisibleForTesting.PRIVA
290304
return object : JsonAdapter<List<Authenticator>> {
291305
override fun fromJson(reader: Reader, metadata: Map<String, Any>): List<Authenticator> {
292306
val allAuthenticators = baseAdapter.fromJson(reader, metadata)
293-
307+
294308
return allAuthenticators.filter { authenticator ->
295309
matchesFactorType(authenticator, factorsAllowed)
296310
}
@@ -313,9 +327,12 @@ public class MfaApiClient @VisibleForTesting(otherwise = VisibleForTesting.PRIVA
313327
* @param factorsAllowed List of allowed factor types
314328
* @return true if the authenticator matches any allowed factor type
315329
*/
316-
private fun matchesFactorType(authenticator: Authenticator, factorsAllowed: List<String>): Boolean {
330+
private fun matchesFactorType(
331+
authenticator: Authenticator,
332+
factorsAllowed: List<String>
333+
): Boolean {
317334
val effectiveType = getEffectiveType(authenticator)
318-
335+
319336
return factorsAllowed.any { factor ->
320337
val normalizedFactor = factor.lowercase(java.util.Locale.ROOT)
321338
when (normalizedFactor) {
@@ -325,7 +342,7 @@ public class MfaApiClient @VisibleForTesting(otherwise = VisibleForTesting.PRIVA
325342
"oob" -> authenticator.authenticatorType == "oob" || authenticator.type == "oob"
326343
"recovery-code" -> effectiveType == "recovery-code"
327344
"push-notification" -> effectiveType == "push-notification"
328-
else -> effectiveType == normalizedFactor ||
345+
else -> effectiveType == normalizedFactor ||
329346
authenticator.authenticatorType?.lowercase(java.util.Locale.ROOT) == normalizedFactor ||
330347
authenticator.type.lowercase(java.util.Locale.ROOT) == normalizedFactor
331348
}
@@ -370,7 +387,7 @@ public class MfaApiClient @VisibleForTesting(otherwise = VisibleForTesting.PRIVA
370387
.addHeader(HEADER_AUTHORIZATION, "Bearer $mfaToken")
371388
.addParameter(AUTHENTICATOR_TYPES_KEY, listOf("oob"))
372389
.addParameter(OOB_CHANNELS_KEY, listOf(oobChannel))
373-
390+
374391
if (phoneNumber != null) {
375392
request.addParameter(PHONE_NUMBER_KEY, phoneNumber)
376393
}
@@ -411,7 +428,7 @@ public class MfaApiClient @VisibleForTesting(otherwise = VisibleForTesting.PRIVA
411428
.setGrantType(GRANT_TYPE_MFA_OOB)
412429
.set(MFA_TOKEN_KEY, mfaToken)
413430
.set(OUT_OF_BAND_CODE_KEY, oobCode)
414-
431+
415432
if (bindingCode != null) {
416433
parametersBuilder.set(BINDING_CODE_KEY, bindingCode)
417434
}
@@ -465,7 +482,6 @@ public class MfaApiClient @VisibleForTesting(otherwise = VisibleForTesting.PRIVA
465482
}
466483

467484

468-
469485
/**
470486
* Creates error adapter for getAuthenticators() operations.
471487
*/
@@ -643,6 +659,7 @@ public class MfaApiClient @VisibleForTesting(otherwise = VisibleForTesting.PRIVA
643659
private const val RECOVERY_CODE_KEY = "recovery_code"
644660
private const val GRANT_TYPE_MFA_OTP = "http://auth0.com/oauth/grant-type/mfa-otp"
645661
private const val GRANT_TYPE_MFA_OOB = "http://auth0.com/oauth/grant-type/mfa-oob"
646-
private const val GRANT_TYPE_MFA_RECOVERY_CODE = "http://auth0.com/oauth/grant-type/mfa-recovery-code"
662+
private const val GRANT_TYPE_MFA_RECOVERY_CODE =
663+
"http://auth0.com/oauth/grant-type/mfa-recovery-code"
647664
}
648665
}

auth0/src/main/java/com/auth0/android/myaccount/MyAccountAPIClient.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,10 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
281281
val url = getDomainUrlBuilder().addPathSegment(AUTHENTICATION_METHODS).build()
282282

283283
val listAdapter = object : JsonAdapter<List<AuthenticationMethod>> {
284-
override fun fromJson(reader: Reader, metadata: Map<String, Any>): List<AuthenticationMethod> {
284+
override fun fromJson(
285+
reader: Reader,
286+
metadata: Map<String, Any>
287+
): List<AuthenticationMethod> {
285288
val container = gson.fromJson(reader, AuthenticationMethods::class.java)
286289
return container.authenticationMethods
287290
}
@@ -848,5 +851,9 @@ public class MyAccountAPIClient @VisibleForTesting(otherwise = VisibleForTesting
848851
}
849852
}
850853
}
854+
855+
init {
856+
factory.setAuth0ClientInfo(auth0.auth0UserAgent.value)
857+
}
851858
}
852859

auth0/src/test/java/com/auth0/android/authentication/MfaApiClientTest.kt

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ package com.auth0.android.authentication
33
import com.auth0.android.Auth0
44
import com.auth0.android.authentication.mfa.MfaApiClient
55
import com.auth0.android.authentication.mfa.MfaEnrollmentType
6+
import com.auth0.android.authentication.mfa.MfaException.MfaChallengeException
7+
import com.auth0.android.authentication.mfa.MfaException.MfaEnrollmentException
8+
import com.auth0.android.authentication.mfa.MfaException.MfaListAuthenticatorsException
9+
import com.auth0.android.authentication.mfa.MfaException.MfaVerifyException
610
import com.auth0.android.authentication.mfa.MfaVerificationType
7-
import com.auth0.android.authentication.mfa.MfaException.*
811
import com.auth0.android.request.internal.ThreadSwitcherShadow
912
import com.auth0.android.result.Authenticator
1013
import com.auth0.android.result.Challenge
1114
import com.auth0.android.result.Credentials
1215
import com.auth0.android.result.EnrollmentChallenge
13-
import com.auth0.android.result.MfaEnrollmentChallenge
1416
import com.auth0.android.result.TotpEnrollmentChallenge
15-
import com.auth0.android.util.CallbackMatcher
1617
import com.auth0.android.util.MockCallback
1718
import com.auth0.android.util.SSLTestUtils
1819
import com.google.gson.Gson
@@ -24,7 +25,12 @@ import okhttp3.mockwebserver.MockResponse
2425
import okhttp3.mockwebserver.MockWebServer
2526
import okhttp3.mockwebserver.RecordedRequest
2627
import org.hamcrest.MatcherAssert.assertThat
27-
import org.hamcrest.Matchers.*
28+
import org.hamcrest.Matchers.containsString
29+
import org.hamcrest.Matchers.hasSize
30+
import org.hamcrest.Matchers.instanceOf
31+
import org.hamcrest.Matchers.`is`
32+
import org.hamcrest.Matchers.notNullValue
33+
import org.hamcrest.Matchers.nullValue
2834
import org.junit.After
2935
import org.junit.Assert.assertThrows
3036
import org.junit.Before
@@ -69,7 +75,11 @@ public class MfaApiClientTest {
6975
)
7076
}
7177

72-
private fun enqueueErrorResponse(error: String, description: String, statusCode: Int = 400): Unit {
78+
private fun enqueueErrorResponse(
79+
error: String,
80+
description: String,
81+
statusCode: Int = 400
82+
): Unit {
7383
val json = """{"error": "$error", "error_description": "$description"}"""
7484
enqueueMockResponse(json, statusCode)
7585
}
@@ -87,6 +97,51 @@ public class MfaApiClientTest {
8797
}
8898

8999

100+
@Test
101+
public fun shouldIncludeAuth0ClientHeaderInGetAuthenticators(): Unit = runTest {
102+
val json = """[{"id": "sms|dev_123", "type": "oob", "active": true}]"""
103+
enqueueMockResponse(json)
104+
105+
mfaClient.getAuthenticators(listOf("oob")).await()
106+
107+
val request = mockServer.takeRequest()
108+
assertThat(request.getHeader("Auth0-Client"), `is`(notNullValue()))
109+
}
110+
111+
@Test
112+
public fun shouldIncludeAuth0ClientHeaderInEnroll(): Unit = runTest {
113+
val json = """{"id": "sms|dev_123", "auth_session": "session_abc"}"""
114+
enqueueMockResponse(json)
115+
116+
mfaClient.enroll(MfaEnrollmentType.Phone("+12025550135")).await()
117+
118+
val request = mockServer.takeRequest()
119+
assertThat(request.getHeader("Auth0-Client"), `is`(notNullValue()))
120+
}
121+
122+
@Test
123+
public fun shouldIncludeAuth0ClientHeaderInChallenge(): Unit = runTest {
124+
val json = """{"challenge_type": "oob", "oob_code": "oob_123"}"""
125+
enqueueMockResponse(json)
126+
127+
mfaClient.challenge("sms|dev_123").await()
128+
129+
val request = mockServer.takeRequest()
130+
assertThat(request.getHeader("Auth0-Client"), `is`(notNullValue()))
131+
}
132+
133+
@Test
134+
public fun shouldIncludeAuth0ClientHeaderInVerify(): Unit = runTest {
135+
val json =
136+
"""{"access_token": "$ACCESS_TOKEN", "id_token": "$ID_TOKEN", "token_type": "Bearer", "expires_in": 86400}"""
137+
enqueueMockResponse(json)
138+
139+
mfaClient.verify(MfaVerificationType.Otp("123456")).await()
140+
141+
val request = mockServer.takeRequest()
142+
assertThat(request.getHeader("Auth0-Client"), `is`(notNullValue()))
143+
}
144+
90145
@Test
91146
public fun shouldGetAuthenticatorsSuccess(): Unit = runTest {
92147
val json = """[
@@ -436,7 +491,8 @@ public class MfaApiClientTest {
436491

437492
@Test
438493
public fun shouldVerifyOtpWithCorrectGrantType(): Unit = runTest {
439-
val json = """{"access_token": "$ACCESS_TOKEN", "id_token": "$ID_TOKEN", "token_type": "Bearer", "expires_in": 86400}"""
494+
val json =
495+
"""{"access_token": "$ACCESS_TOKEN", "id_token": "$ID_TOKEN", "token_type": "Bearer", "expires_in": 86400}"""
440496
enqueueMockResponse(json)
441497

442498
mfaClient.verify(MfaVerificationType.Otp("123456")).await()
@@ -500,21 +556,25 @@ public class MfaApiClientTest {
500556

501557
@Test
502558
public fun shouldVerifyOobWithoutBindingCodeSuccess(): Unit = runTest {
503-
val json = """{"access_token": "$ACCESS_TOKEN", "id_token": "$ID_TOKEN", "token_type": "Bearer", "expires_in": 86400}"""
559+
val json =
560+
"""{"access_token": "$ACCESS_TOKEN", "id_token": "$ID_TOKEN", "token_type": "Bearer", "expires_in": 86400}"""
504561
enqueueMockResponse(json)
505562

506-
val credentials = mfaClient.verify(MfaVerificationType.Oob(oobCode = "oob_code_123")).await()
563+
val credentials =
564+
mfaClient.verify(MfaVerificationType.Oob(oobCode = "oob_code_123")).await()
507565

508566
assertThat(credentials, `is`(notNullValue()))
509567
assertThat(credentials.accessToken, `is`(ACCESS_TOKEN))
510568
}
511569

512570
@Test
513571
public fun shouldVerifyOobWithCorrectParameters(): Unit = runTest {
514-
val json = """{"access_token": "$ACCESS_TOKEN", "id_token": "$ID_TOKEN", "token_type": "Bearer", "expires_in": 86400}"""
572+
val json =
573+
"""{"access_token": "$ACCESS_TOKEN", "id_token": "$ID_TOKEN", "token_type": "Bearer", "expires_in": 86400}"""
515574
enqueueMockResponse(json)
516575

517-
mfaClient.verify(MfaVerificationType.Oob(oobCode = "oob_code_123", bindingCode = "654321")).await()
576+
mfaClient.verify(MfaVerificationType.Oob(oobCode = "oob_code_123", bindingCode = "654321"))
577+
.await()
518578

519579
val request = mockServer.takeRequest()
520580
assertThat(request.path, `is`("/oauth/token"))
@@ -530,7 +590,8 @@ public class MfaApiClientTest {
530590

531591
@Test
532592
public fun shouldVerifyOobWithoutBindingCodeInRequest(): Unit = runTest {
533-
val json = """{"access_token": "$ACCESS_TOKEN", "id_token": "$ID_TOKEN", "token_type": "Bearer", "expires_in": 86400}"""
593+
val json =
594+
"""{"access_token": "$ACCESS_TOKEN", "id_token": "$ID_TOKEN", "token_type": "Bearer", "expires_in": 86400}"""
534595
enqueueMockResponse(json)
535596

536597
mfaClient.verify(MfaVerificationType.Oob(oobCode = "oob_code_123")).await()
@@ -565,7 +626,8 @@ public class MfaApiClientTest {
565626
}"""
566627
enqueueMockResponse(json)
567628

568-
val credentials = mfaClient.verify(MfaVerificationType.RecoveryCode("OLD_RECOVERY_CODE")).await()
629+
val credentials =
630+
mfaClient.verify(MfaVerificationType.RecoveryCode("OLD_RECOVERY_CODE")).await()
569631

570632
assertThat(credentials, `is`(notNullValue()))
571633
assertThat(credentials.accessToken, `is`(ACCESS_TOKEN))
@@ -574,7 +636,8 @@ public class MfaApiClientTest {
574636

575637
@Test
576638
public fun shouldVerifyRecoveryCodeWithCorrectParameters(): Unit = runTest {
577-
val json = """{"access_token": "$ACCESS_TOKEN", "id_token": "$ID_TOKEN", "token_type": "Bearer", "expires_in": 86400}"""
639+
val json =
640+
"""{"access_token": "$ACCESS_TOKEN", "id_token": "$ID_TOKEN", "token_type": "Bearer", "expires_in": 86400}"""
578641
enqueueMockResponse(json)
579642

580643
mfaClient.verify(MfaVerificationType.RecoveryCode("RECOVERY_123")).await()
@@ -671,7 +734,8 @@ public class MfaApiClientTest {
671734

672735
@Test
673736
public fun shouldVerifyOtpWithCallback(): Unit {
674-
val json = """{"access_token": "$ACCESS_TOKEN", "id_token": "$ID_TOKEN", "token_type": "Bearer", "expires_in": 86400}"""
737+
val json =
738+
"""{"access_token": "$ACCESS_TOKEN", "id_token": "$ID_TOKEN", "token_type": "Bearer", "expires_in": 86400}"""
675739
enqueueMockResponse(json)
676740

677741
val callback = MockCallback<Credentials, MfaVerifyException>()
@@ -763,8 +827,10 @@ public class MfaApiClientTest {
763827
private companion object {
764828
private const val CLIENT_ID = "CLIENT_ID"
765829
private const val MFA_TOKEN = "MFA_TOKEN_123"
766-
private const val ACCESS_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U"
767-
private const val ID_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.Gfx6VO9tcxwk6xqx9yYzSfebfeakZp5JYIgP_edcw_A"
830+
private const val ACCESS_TOKEN =
831+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U"
832+
private const val ID_TOKEN =
833+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.Gfx6VO9tcxwk6xqx9yYzSfebfeakZp5JYIgP_edcw_A"
768834
private const val REFRESH_TOKEN = "REFRESH_TOKEN"
769835
}
770836
}

0 commit comments

Comments
 (0)