Skip to content

Commit af3d85a

Browse files
@W-18777804: [qr code scan][android] we should prevent a successful login of client A by scanning QR code of client B (#2731)
1 parent 271e273 commit af3d85a

3 files changed

Lines changed: 123 additions & 1 deletion

File tree

libs/SalesforceSDK/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<string name="app_name">SalesforceSDK</string>
88
<string name="account_type">com.salesforce.androidsdk</string>
99
<string name="app_package">com.salesforce.androidsdk</string>
10+
<string name="cannot_use_another_apps_login_qr_code">Cannot use another app\'s login QR Code. Please log in to this app.</string>
1011

1112
<!-- If you're only supporting recent versions of Android (e.g. 3.x and up), you can override this to be touch and get a better looking login UI -->
1213
<string name="oauth_display_type">touch</string>

libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/LoginActivity.kt

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ import com.salesforce.androidsdk.R.color.sf__background
106106
import com.salesforce.androidsdk.R.color.sf__background_dark
107107
import com.salesforce.androidsdk.R.color.sf__primary_color
108108
import com.salesforce.androidsdk.R.drawable.sf__action_back
109+
import com.salesforce.androidsdk.R.string.cannot_use_another_apps_login_qr_code
109110
import com.salesforce.androidsdk.R.string.sf__biometric_opt_in_title
110111
import com.salesforce.androidsdk.R.string.sf__generic_authentication_error_title
111112
import com.salesforce.androidsdk.R.string.sf__jwt_authentication_error
@@ -208,7 +209,6 @@ open class LoginActivity : FragmentActivity() {
208209
* plus the optional web server flow code verifier accompanying the
209210
* frontdoor bridge URL.
210211
*/
211-
viewModel.isUsingFrontDoorBridge = isFrontdoorBridgeUrlIntent(intent) || isQrCodeLoginUrlIntent(intent)
212212
val uiBridgeApiParameters = if (isQrCodeLoginUrlIntent(intent)) {
213213
uiBridgeApiParametersFromQrCodeLoginUrl(intent.data?.toString())
214214
} else intent.getStringExtra(EXTRA_KEY_FRONTDOOR_BRIDGE_URL)?.let { frontdoorBridgeUrl ->
@@ -218,6 +218,29 @@ open class LoginActivity : FragmentActivity() {
218218
)
219219
}
220220

221+
/**
222+
* The Salesforce Connected App or External Client App consumer key
223+
* from the Salesforce Identity API UI Bridge front door URL. This
224+
* is sometimes known as "client id" or "remote access consumer
225+
* key".
226+
*/
227+
val uiBridgeApiParametersConsumerKey = uiBridgeApiParameters?.frontdoorBridgeUrl?.toUri()?.getQueryParameter("startURL")?.toUri()?.getQueryParameter("client_id")
228+
229+
// Choose front door bridge use by verifying intent data and such that only front door bridge URLs with matching consumer keys are used.
230+
val uiBridgeApiParametersFrontDoorBridgeUrlMismatchedConsumerKey = uiBridgeApiParametersConsumerKey != null && uiBridgeApiParametersConsumerKey != viewModel.bootConfig.remoteAccessConsumerKey
231+
viewModel.isUsingFrontDoorBridge = (isFrontdoorBridgeUrlIntent(intent) || isQrCodeLoginUrlIntent(intent)) && !uiBridgeApiParametersFrontDoorBridgeUrlMismatchedConsumerKey
232+
233+
// Alert the user if the front door bridge URL is not for this app and was discarded.
234+
if (uiBridgeApiParametersFrontDoorBridgeUrlMismatchedConsumerKey) {
235+
runOnUiThread {
236+
makeText(
237+
this,
238+
getString(cannot_use_another_apps_login_qr_code),
239+
LENGTH_LONG
240+
).show()
241+
}
242+
}
243+
221244
// Don't let sharedBrowserSession org setting stop a new user from logging in.
222245
if (intent.extras?.getBoolean(NEW_USER) == true) {
223246
newUserIntent = true

libs/test/SalesforceSDKTest/src/com/salesforce/androidsdk/ui/LoginActivityTest.kt

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,16 @@ package com.salesforce.androidsdk.ui
2828

2929
import android.content.Intent
3030
import android.net.Uri.parse
31+
import androidx.core.net.toUri
3132
import androidx.test.core.app.ActivityScenario.launch
3233
import androidx.test.core.app.ApplicationProvider.getApplicationContext
3334
import androidx.test.ext.junit.runners.AndroidJUnit4
3435
import com.salesforce.androidsdk.app.SalesforceSDKManager
3536
import com.salesforce.androidsdk.ui.LoginActivity.Companion.EXTRA_KEY_LOGIN_HINT
3637
import com.salesforce.androidsdk.ui.LoginActivity.Companion.EXTRA_KEY_LOGIN_HOST
3738
import org.junit.Assert.assertEquals
39+
import org.junit.Assert.assertFalse
40+
import org.junit.Assert.assertTrue
3841
import org.junit.Test
3942
import org.junit.runner.RunWith
4043

@@ -65,4 +68,99 @@ class LoginActivityTest {
6568
}
6669
}
6770
}
71+
72+
@Test
73+
fun viewModelIsUsingFrontDoorBridge_DefaultValue_onCreateWithoutQrCodeLoginIntent() {
74+
launch<LoginActivity>(
75+
Intent(
76+
getApplicationContext(),
77+
LoginActivity::class.java
78+
)
79+
).use { activityScenario ->
80+
81+
activityScenario.onActivity { activity ->
82+
83+
assertFalse(activity.viewModel.isUsingFrontDoorBridge)
84+
}
85+
}
86+
}
87+
88+
@Test
89+
fun viewModelFrontDoorBridgeCodeVerifier_UpdatesOn_onCreateWithQrCodeLoginIntent() {
90+
val uri = "app://android/login/qr/?bridgeJson=%7B%22pkce_code_verifier%22%3A%22__CODE_VERIFIER__%22%2C%22frontdoor_bridge_url%22%3A%22https%3A%2F%2Fmobilesdk.my.salesforce.com%2Fsecur%2Ffrontdoor.jsp%3Fotp%3D__OTP__%26startURL%3D%252Fservices%252Foauth2%252Fauthorize%253Fresponse_type%253Dcode%2526client_id%253D__CONSUMER_KEY__%2526redirect_uri%253Dtestsfdc%25253A%25252F%25252F%25252Fmobilesdk%25252Fdetect%25252Foauth%25252Fdone%2526code_challenge%253D__CODE_CHALLENGE__%26cshc%3D__CSHC__%22%7D".toUri()
91+
92+
launch<LoginActivity>(
93+
Intent(
94+
getApplicationContext(),
95+
LoginActivity::class.java
96+
).apply {
97+
data = uri
98+
}).use { activityScenario ->
99+
100+
activityScenario.onActivity { activity ->
101+
102+
assertTrue(activity.viewModel.isUsingFrontDoorBridge)
103+
assertEquals("__CODE_VERIFIER__", activity.viewModel.frontdoorBridgeCodeVerifier)
104+
assertEquals("https://mobilesdk.my.salesforce.com", activity.viewModel.frontdoorBridgeServer)
105+
assertEquals("https://mobilesdk.my.salesforce.com/secur/frontdoor.jsp?otp=__OTP__&startURL=%2Fservices%2Foauth2%2Fauthorize%3Fresponse_type%3Dcode%26client_id%3D__CONSUMER_KEY__%26redirect_uri%3Dtestsfdc%253A%252F%252F%252Fmobilesdk%252Fdetect%252Foauth%252Fdone%26code_challenge%3D__CODE_CHALLENGE__&cshc=__CSHC__", activity.viewModel.loginUrl.value)
106+
}
107+
}
108+
}
109+
110+
@Test
111+
fun viewModelIsUsingFrontDoorBridge_DefaultValue_onCreateWithQrCodeLoginIntentAndMismatchedConsumerKey() {
112+
val uri = "app://android/login/qr/?bridgeJson=%7B%22pkce_code_verifier%22%3A%22__CODE_VERIFIER__%22%2C%22frontdoor_bridge_url%22%3A%22https%3A%2F%2Fmobilesdk.my.salesforce.com%2Fsecur%2Ffrontdoor.jsp%3Fotp%3D__OTP__%26startURL%3D%252Fservices%252Foauth2%252Fauthorize%253Fresponse_type%253Dcode%2526client_id%253D__MISMATCHED_CONSUMER_KEY__%2526redirect_uri%253Dtestsfdc%25253A%25252F%25252F%25252Fmobilesdk%25252Fdetect%25252Foauth%25252Fdone%2526code_challenge%253D__CODE_CHALLENGE__%26cshc%3D__CSHC__%22%7D".toUri()
113+
114+
launch<LoginActivity>(
115+
Intent(
116+
getApplicationContext(),
117+
LoginActivity::class.java
118+
).apply {
119+
data = uri
120+
}).use { activityScenario ->
121+
122+
activityScenario.onActivity { activity ->
123+
124+
assertFalse(activity.viewModel.isUsingFrontDoorBridge)
125+
}
126+
}
127+
}
128+
129+
@Test
130+
fun viewModelIsUsingFrontDoorBridge_UpdatesOn_onCreateWithQrCodeLoginIntentAndMissingStartUrl() {
131+
val uri = "app://android/login/qr/?bridgeJson=%7B%22pkce_code_verifier%22%3A%22__CODE_VERIFIER__%22%2C%22frontdoor_bridge_url%22%3A%22https%3A%2F%2Fmobilesdk.my.salesforce.com%2Fsecur%2Ffrontdoor.jsp%3Fotp%3D__OTP__%26missingStartURL%3D%252Fservices%252Foauth2%252Fauthorize%253Fresponse_type%253Dcode%2526client_id%253D__CONSUMER_KEY__%2526redirect_uri%253Dtestsfdc%25253A%25252F%25252F%25252Fmobilesdk%25252Fdetect%25252Foauth%25252Fdone%2526code_challenge%253D__CODE_CHALLENGE__%26cshc%3D__CSHC__%22%7D".toUri()
132+
133+
launch<LoginActivity>(
134+
Intent(
135+
getApplicationContext(),
136+
LoginActivity::class.java
137+
).apply {
138+
data = uri
139+
}).use { activityScenario ->
140+
141+
activityScenario.onActivity { activity ->
142+
143+
assertTrue(activity.viewModel.isUsingFrontDoorBridge)
144+
}
145+
}
146+
}
147+
148+
@Test
149+
fun viewModelIsUsingFrontDoorBridge_UpdatesOn_onCreateWithQrCodeLoginIntentAndMissingStartUrlClientId() {
150+
val uri = "app://android/login/qr/?bridgeJson=%7B%22pkce_code_verifier%22%3A%22__CODE_VERIFIER__%22%2C%22frontdoor_bridge_url%22%3A%22https%3A%2F%2Fmobilesdk.my.salesforce.com%2Fsecur%2Ffrontdoor.jsp%3Fotp%3D__OTP__%26startURL%3D%252Fservices%252Foauth2%252Fauthorize%253Fresponse_type%253Dcode%2526missing_client_id%253D__CONSUMER_KEY__%2526redirect_uri%253Dtestsfdc%25253A%25252F%25252F%25252Fmobilesdk%25252Fdetect%25252Foauth%25252Fdone%2526code_challenge%253D__CODE_CHALLENGE__%26cshc%3D__CSHC__%22%7D".toUri()
151+
152+
launch<LoginActivity>(
153+
Intent(
154+
getApplicationContext(),
155+
LoginActivity::class.java
156+
).apply {
157+
data = uri
158+
}).use { activityScenario ->
159+
160+
activityScenario.onActivity { activity ->
161+
162+
assertTrue(activity.viewModel.isUsingFrontDoorBridge)
163+
}
164+
}
165+
}
68166
}

0 commit comments

Comments
 (0)