Skip to content

Commit a8ec0dd

Browse files
@W-19100006: [Android] QR code login not working with webserver flow disabled (#2737)
1 parent 395a7f9 commit a8ec0dd

2 files changed

Lines changed: 110 additions & 63 deletions

File tree

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

Lines changed: 88 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -215,46 +215,7 @@ open class LoginActivity : FragmentActivity() {
215215
SalesforceSDKManager.getInstance().setViewNavigationVisibility(this)
216216
}
217217

218-
// If the intent is for Salesforce Welcome Discovery, apply it to the activity.
219-
applySalesforceWelcomeDiscoveryIntent(intent)
220-
221-
/*
222-
* For Salesforce Identity API UI Bridge support, the overriding
223-
* frontdoor bridge URL to use in place of the default initial login URL
224-
* plus the optional web server flow code verifier accompanying the
225-
* frontdoor bridge URL.
226-
*/
227-
val uiBridgeApiParameters = if (isQrCodeLoginUrlIntent(intent)) {
228-
uiBridgeApiParametersFromQrCodeLoginUrl(intent.data?.toString())
229-
} else intent.getStringExtra(EXTRA_KEY_FRONTDOOR_BRIDGE_URL)?.let { frontdoorBridgeUrl ->
230-
UiBridgeApiParameters(
231-
frontdoorBridgeUrl,
232-
intent.getStringExtra(EXTRA_KEY_PKCE_CODE_VERIFIER)
233-
)
234-
}
235-
236-
/*
237-
* The Salesforce Connected App or External Client App consumer key
238-
* from the Salesforce Identity API UI Bridge front door URL. This
239-
* is sometimes known as "client id" or "remote access consumer
240-
* key".
241-
*/
242-
val uiBridgeApiParametersConsumerKey = uiBridgeApiParameters?.frontdoorBridgeUrl?.toUri()?.getQueryParameter("startURL")?.toUri()?.getQueryParameter("client_id")
243-
244-
// Choose front door bridge use by verifying intent data and such that only front door bridge URLs with matching consumer keys are used.
245-
val uiBridgeApiParametersFrontDoorBridgeUrlMismatchedConsumerKey = uiBridgeApiParametersConsumerKey != null && uiBridgeApiParametersConsumerKey != viewModel.bootConfig.remoteAccessConsumerKey
246-
viewModel.isUsingFrontDoorBridge = (isFrontdoorBridgeUrlIntent(intent) || isQrCodeLoginUrlIntent(intent)) && !uiBridgeApiParametersFrontDoorBridgeUrlMismatchedConsumerKey
247-
248-
// Alert the user if the front door bridge URL is not for this app and was discarded.
249-
if (uiBridgeApiParametersFrontDoorBridgeUrlMismatchedConsumerKey) {
250-
runOnUiThread {
251-
makeText(
252-
this,
253-
getString(cannot_use_another_apps_login_qr_code),
254-
LENGTH_LONG
255-
).show()
256-
}
257-
}
218+
applyIntent(intent)
258219

259220
// Don't let sharedBrowserSession org setting stop a new user from logging in.
260221
if (intent.extras?.getBoolean(NEW_USER) == true) {
@@ -289,16 +250,9 @@ open class LoginActivity : FragmentActivity() {
289250
presentBiometric()
290251
}
291252

292-
// Prompt user with the default login page or log in via other configurations such as using
293-
// a Salesforce Identity API UI Bridge front door URL.
294-
when {
295-
viewModel.isUsingFrontDoorBridge && uiBridgeApiParameters?.frontdoorBridgeUrl != null ->
296-
loginWithFrontdoorBridgeUrl(
297-
uiBridgeApiParameters.frontdoorBridgeUrl,
298-
uiBridgeApiParameters.pkceCodeVerifier
299-
)
300-
301-
else -> certAuthOrLogin()
253+
// Prompt user with the default login page when not using a Salesforce Identity API UI Bridge front door URL.
254+
if (!viewModel.isUsingFrontDoorBridge) {
255+
certAuthOrLogin()
302256
}
303257

304258
// Take control of the back logic if the device is locked.
@@ -387,13 +341,12 @@ open class LoginActivity : FragmentActivity() {
387341
super.onNewIntent(intent)
388342

389343
// If the intent is a callback from Chrome and not another recognized intent URL, process it and do nothing else.
390-
if (isCustomTabAuthFinishedCallback(intent) && intent.data?.let { (isSalesforceWelcomeDiscoveryMobileUrl(this, it)) } != true) {
344+
if (isCustomTabAuthFinishedCallback(intent) && intent.data?.let { (isQrCodeLoginUrlIntent(intent) || isSalesforceWelcomeDiscoveryMobileUrl(this, it)) } != true) {
391345
completeAdvAuthFlow(intent)
392346
return
393347
}
394348

395-
// If the intent is for Salesforce Welcome Discovery, apply it to the activity.
396-
applySalesforceWelcomeDiscoveryIntent(intent)
349+
applyIntent(intent)
397350
}
398351

399352
private fun clearWebView(showServerPicker: Boolean = true) {
@@ -444,7 +397,7 @@ open class LoginActivity : FragmentActivity() {
444397
}
445398
}
446399

447-
// region QR Code Login Via UI Bridge API Public Implementation
400+
// region Log In Via Salesforce Identity API UI Bridge Front Door URL Public Implementation
448401

449402
/**
450403
* Automatically log in with a UI Bridge API front door bridge URL and PKCE
@@ -502,7 +455,7 @@ open class LoginActivity : FragmentActivity() {
502455
}
503456

504457
// endregion
505-
// End of Public Functions
458+
// region End Of Public Methods
506459

507460
protected open fun certAuthOrLogin() {
508461
when {
@@ -627,7 +580,8 @@ open class LoginActivity : FragmentActivity() {
627580
}
628581
}
629582

630-
// End of Public API (protected)
583+
// endregion
584+
// region End Of Public API (protected)
631585

632586
private fun isCustomTabAuthFinishedCallback(intent: Intent): Boolean {
633587
return intent.data != null
@@ -684,7 +638,9 @@ open class LoginActivity : FragmentActivity() {
684638
}
685639
}
686640

687-
// Biometric Authentication Code
641+
// endregion
642+
// region Biometric Authentication Code
643+
688644
private fun presentBiometric() {
689645
val biometricPrompt = biometricPrompt
690646
val biometricManager = BiometricManager.from(this)
@@ -888,6 +844,65 @@ open class LoginActivity : FragmentActivity() {
888844
)
889845
}
890846

847+
// endregion
848+
// region Log In Via Salesforce Identity API UI Bridge Front Door URL Private Implementation
849+
850+
851+
/**
852+
* If the intent is for log in via Salesforce Identity API UI Bridge API
853+
* front door URL, apply it to the activity.
854+
* @param intent The intent
855+
*/
856+
private fun applyUiBridgeApiFrontDoorUrl(intent: Intent) {
857+
858+
/*
859+
* For Salesforce Identity API UI Bridge support, the overriding
860+
* frontdoor bridge URL to use in place of the default initial login URL
861+
* plus the optional web server flow code verifier accompanying the
862+
* frontdoor bridge URL.
863+
*/
864+
val uiBridgeApiParameters = if (isQrCodeLoginUrlIntent(intent)) {
865+
uiBridgeApiParametersFromQrCodeLoginUrl(intent.data?.toString())
866+
} else intent.getStringExtra(EXTRA_KEY_FRONTDOOR_BRIDGE_URL)?.let { frontdoorBridgeUrl ->
867+
UiBridgeApiParameters(
868+
frontdoorBridgeUrl,
869+
intent.getStringExtra(EXTRA_KEY_PKCE_CODE_VERIFIER)
870+
)
871+
}
872+
873+
/*
874+
* The Salesforce Connected App or External Client App consumer key
875+
* from the Salesforce Identity API UI Bridge front door URL. This
876+
* is sometimes known as "client id" or "remote access consumer
877+
* key".
878+
*/
879+
val uiBridgeApiParametersConsumerKey = uiBridgeApiParameters?.frontdoorBridgeUrl?.toUri()?.getQueryParameter("startURL")?.toUri()?.getQueryParameter("client_id")
880+
881+
// Choose front door bridge use by verifying intent data and such that only front door bridge URLs with matching consumer keys are used.
882+
val uiBridgeApiParametersFrontDoorBridgeUrlMismatchedConsumerKey = uiBridgeApiParametersConsumerKey != null && uiBridgeApiParametersConsumerKey != viewModel.bootConfig.remoteAccessConsumerKey
883+
viewModel.isUsingFrontDoorBridge = (isFrontdoorBridgeUrlIntent(intent) || isQrCodeLoginUrlIntent(intent)) && !uiBridgeApiParametersFrontDoorBridgeUrlMismatchedConsumerKey
884+
885+
// Alert the user if the front door bridge URL is not for this app and was discarded.
886+
if (uiBridgeApiParametersFrontDoorBridgeUrlMismatchedConsumerKey) {
887+
runOnUiThread {
888+
makeText(
889+
this,
890+
getString(cannot_use_another_apps_login_qr_code),
891+
LENGTH_LONG
892+
).show()
893+
}
894+
}
895+
896+
// Use the front door URL as the login page if applicable.
897+
if (viewModel.isUsingFrontDoorBridge && uiBridgeApiParameters?.frontdoorBridgeUrl != null) {
898+
loginWithFrontdoorBridgeUrl(
899+
uiBridgeApiParameters.frontdoorBridgeUrl,
900+
uiBridgeApiParameters.pkceCodeVerifier
901+
)
902+
}
903+
}
904+
905+
// endregion
891906
// region Salesforce Welcome Login Private Implementation
892907

893908
/**
@@ -1039,6 +1054,20 @@ open class LoginActivity : FragmentActivity() {
10391054

10401055
// endregion
10411056

1057+
/**
1058+
* Applies a new intent to the activity, for instance when the activity is
1059+
* created or receives a new intent.
1060+
* @param intent The new intent
1061+
*/
1062+
private fun applyIntent(intent: Intent) {
1063+
1064+
// If the intent is for Salesforce Welcome Discovery, apply it to the activity.
1065+
applySalesforceWelcomeDiscoveryIntent(intent)
1066+
1067+
// If the intent is for log in using a UI Bridge API front door URL, apply it to the activity.
1068+
applyUiBridgeApiFrontDoorUrl(intent)
1069+
}
1070+
10421071
/**
10431072
* A web view client which intercepts the redirect to the OAuth callback URL. That redirect marks the end of
10441073
* the user facing portion of the authentication flow.
@@ -1221,7 +1250,7 @@ open class LoginActivity : FragmentActivity() {
12211250
"(function() { return window.getComputedStyle(document.body, null).getPropertyValue('background-color'); })();"
12221251

12231252
// endregion
1224-
// region QR Code Login Via Salesforce Identity API UI Bridge Public Implementation
1253+
// region Log In Via Salesforce Identity API UI Bridge Front Door URL Public Implementation
12251254

12261255
/**
12271256
* For QR code login URLs, the URL path which distinguishes them from other URLs provided by
@@ -1328,7 +1357,7 @@ open class LoginActivity : FragmentActivity() {
13281357
const val EXTRA_KEY_LOGIN_HOST = "login_host"
13291358

13301359
// endregion
1331-
// region QR Code Login Via Salesforce Identity API UI Bridge Private Implementation
1360+
// region Log In Via Salesforce Identity API UI Bridge Front Door URL Private Implementation
13321361

13331362
/** Extras key for the Salesforce Identity API UI Bridge front door URL */
13341363
const val EXTRA_KEY_FRONTDOOR_BRIDGE_URL = "frontdoor_bridge_url"

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,30 @@ open class LoginViewModel(val bootConfig: BootConfig) : ViewModel() {
144144
protected open val authorizationDisplayType =
145145
SalesforceSDKManager.getInstance().appContext.getString(oauth_display_type)
146146

147+
/**
148+
* Determines use of OAuth 2.0 Web Server Flow or User-Agent Flow.
149+
*
150+
* When a Salesforce Identity API UI Bridge Front-Door URL is in use for log
151+
* in and it has a PKCE/Code verifier Web Server Flow will be Enabled. For
152+
* a Front-Door Bridge URL without a PKCE/Code Verifier User-Agent Flow will
153+
* be enabled.
154+
*
155+
* When no Front-Door Bridge URL is in use, Web Server Flow is
156+
* enabled when web server authentication or browser login are enabled.
157+
* @return True if Web Server Flow is enabled, false if User-Agent Flow is
158+
* enabled.
159+
*/
147160
internal val useWebServerFlow: Boolean
148161
get() = with(SalesforceSDKManager.getInstance()) {
149-
// Browser based authentication requires the Web Server flow for PKCE security.
150-
(useWebServerAuthentication || isBrowserLoginEnabled)
151-
// QR Code login may require User Agent flow.
152-
&& !(isUsingFrontDoorBridge && frontdoorBridgeCodeVerifier == null)
162+
// First, an in-use Salesforce Identity API UI Bridge front-door bridge URL takes precedence.
163+
if (isUsingFrontDoorBridge) {
164+
// A front-door bridge URL accompanied by a PKCE code verifier requires Web Server Flow. Otherwise, User Agent-Flow must be used.
165+
frontdoorBridgeCodeVerifier != null
166+
}
167+
// Second, when not using a front-door bridge URL, the app's preferences can be used.
168+
else {
169+
useWebServerAuthentication || isBrowserLoginEnabled
170+
}
153171
}
154172

155173
/**

0 commit comments

Comments
 (0)