Skip to content

Commit 652e8fc

Browse files
@W-21933885: [MSDK Android] App Attestation Implementation (First Attempt At Testing App Attestation In NativeLoginManager.login())
1 parent db162e1 commit 652e8fc

2 files changed

Lines changed: 61 additions & 3 deletions

File tree

libs/SalesforceSDK/src/com/salesforce/androidsdk/auth/NativeLoginManager.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import com.salesforce.androidsdk.auth.interfaces.NativeLoginResult.Success
6868
import com.salesforce.androidsdk.auth.interfaces.NativeLoginResult.UnknownError
6969
import com.salesforce.androidsdk.auth.interfaces.OtpRequestResult
7070
import com.salesforce.androidsdk.auth.interfaces.OtpVerificationMethod
71+
import com.salesforce.androidsdk.rest.RestClient
7172
import com.salesforce.androidsdk.rest.RestClient.AsyncRequestCallback
7273
import com.salesforce.androidsdk.rest.RestRequest
7374
import com.salesforce.androidsdk.rest.RestRequest.RestEndpoint.LOGIN
@@ -102,14 +103,18 @@ import kotlin.coroutines.suspendCoroutine
102103
* Google Cloud Console. Defaults to null to disable reCAPTCHA use
103104
* @param isReCaptchaEnterprise Specifies if reCAPTCHA uses the enterprise
104105
* license. Defaults to false to disable reCAPTCHA use
106+
* @param restClient The REST client to use for making network requests. This
107+
* parameter is intended for testing purposes only. Defaults to the
108+
* unauthenticated REST client
105109
*/
106110
internal class NativeLoginManager(
107111
private val clientId: String,
108112
private val redirectUri: String,
109113
private val loginUrl: String,
110114
private val reCaptchaSiteKeyId: String? = null,
111115
private val googleCloudProjectId: String? = null,
112-
private val isReCaptchaEnterprise: Boolean = false
116+
private val isReCaptchaEnterprise: Boolean = false,
117+
private val restClient: RestClient = getInstance().clientManager.peekUnauthenticatedRestClient()
113118
) : NativeLoginManager {
114119

115120
private val accountManager = SalesforceSDKManager.getInstance().userAccountManager
@@ -226,8 +231,7 @@ internal class NativeLoginManager(
226231

227232
private suspend fun suspendedRestCall(request: RestRequest): RestResponse? {
228233
return suspendCoroutine { continuation ->
229-
SalesforceSDKManager.getInstance().clientManager
230-
.peekUnauthenticatedRestClient().sendAsync(request, object : AsyncRequestCallback {
234+
restClient.sendAsync(request, object : AsyncRequestCallback {
231235

232236
override fun onSuccess(request: RestRequest?, response: RestResponse?) {
233237
continuation.resume(response)

libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/auth/NativeLoginManagerTest.kt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,17 @@ import androidx.test.filters.SmallTest
55
import com.salesforce.androidsdk.accounts.UserAccountManager
66
import com.salesforce.androidsdk.accounts.UserAccountTest
77
import com.salesforce.androidsdk.app.SalesforceSDKManager
8+
import com.salesforce.androidsdk.rest.RestClient
9+
import com.salesforce.androidsdk.rest.RestResponse
810
import com.salesforce.androidsdk.security.BiometricAuthenticationManager
11+
import io.mockk.coEvery
12+
import io.mockk.every
13+
import io.mockk.mockk
14+
import io.mockk.verify
15+
import kotlinx.coroutines.ExperimentalCoroutinesApi
16+
import kotlinx.coroutines.test.advanceUntilIdle
17+
import kotlinx.coroutines.test.runTest
18+
import okhttp3.Call
919
import org.junit.After
1020
import org.junit.Assert
1121
import org.junit.Assert.assertEquals
@@ -99,6 +109,50 @@ class NativeLoginManagerTest {
99109
assertEquals("key1=value1", buffer.readUtf8())
100110
}
101111

112+
// TODO: This test will need additional review. ECJ20260416
113+
@OptIn(ExperimentalCoroutinesApi::class)
114+
@Test
115+
fun nativeLoginManager_login_collectsAppAttestation() = runTest {
116+
117+
val appAttestationClient = mockk<AppAttestationClient>(relaxed = true)
118+
coEvery { appAttestationClient.createSalesforceOAuthAuthorizationAppAttestation() } returns "__TEST_APP_ATTESTATION__"
119+
120+
val salesforceSdkManager = SalesforceSDKManager.getInstance()
121+
salesforceSdkManager.appAttestationClient = appAttestationClient
122+
123+
val restClient = mockk<RestClient>(relaxed = true)
124+
val mockResponse = mockk<RestResponse>(relaxed = true)
125+
every { mockResponse.isSuccess } returns false
126+
every {
127+
restClient.sendAsync(any(), any())
128+
} answers {
129+
val callback = secondArg<RestClient.AsyncRequestCallback>()
130+
callback.onSuccess(firstArg(), mockResponse)
131+
mockk<Call>(relaxed = true)
132+
}
133+
134+
mgr = NativeLoginManager(
135+
clientId = "clientId",
136+
redirectUri = "redirect",
137+
loginUrl = "loginUrl",
138+
restClient = restClient,
139+
)
140+
141+
mgr.login("TestUser@Example.com", "test123456")
142+
143+
advanceUntilIdle()
144+
145+
verify (exactly = 1) {
146+
restClient.sendAsync(match {
147+
runCatching {
148+
val buffer = okio.Buffer()
149+
it.requestBody?.writeTo(buffer)
150+
buffer.readUtf8().contains("attestation=__TEST_APP_ATTESTATION__")
151+
}.getOrDefault(false)
152+
}, any())
153+
}
154+
}
155+
102156
private fun addUserAccount() {
103157
UserAccountManager.getInstance().createAccount(UserAccountTest.createTestAccount())
104158
}

0 commit comments

Comments
 (0)